diff options
author | Ryan Hansberry <rhansby@gmail.com> | 2015-09-22 10:41:37 -0700 |
---|---|---|
committer | Ryan Hansberry <rhansby@gmail.com> | 2015-09-22 11:59:13 -0700 |
commit | 0f293ab7927470ab3a2d78064cc3c66e2523415f (patch) | |
tree | 0f503c5f09b19df25d9ec7799ac542ea866792d4 /app/src/main | |
parent | d2164043d4748b0269937cb983929c6c2e8c6fff (diff) | |
download | connectbot-0f293ab7927470ab3a2d78064cc3c66e2523415f.tar.gz connectbot-0f293ab7927470ab3a2d78064cc3c66e2523415f.tar.bz2 connectbot-0f293ab7927470ab3a2d78064cc3c66e2523415f.zip |
Convert PubkeyListActivity, PortFowardListActivity, and HostListActivity to AppCompat, using AppCompatListActivity superclass
Diffstat (limited to 'app/src/main')
5 files changed, 646 insertions, 668 deletions
diff --git a/app/src/main/java/org/connectbot/AppCompatListActivity.java b/app/src/main/java/org/connectbot/AppCompatListActivity.java new file mode 100644 index 0000000..6eb406d --- /dev/null +++ b/app/src/main/java/org/connectbot/AppCompatListActivity.java @@ -0,0 +1,108 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2015 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.annotation.VisibleForTesting; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; + +public class AppCompatListActivity extends AppCompatActivity { + protected ItemAdapter mAdapter; + + protected View mEmptyView; + protected RecyclerView mListView; + + /** + * If the list is empty, hides the list and shows the empty message; otherwise, shows + * the list and hides the empty message. + */ + protected void adjustViewVisibility() { + boolean isEmpty = mAdapter.getItemCount() == 0; + mListView.setVisibility(isEmpty ? View.GONE : View.VISIBLE); + mEmptyView.setVisibility(isEmpty ? View.VISIBLE : View.GONE); + } + + /** + * Item decorations for list items, which adds a divider between items and leaves a + * small offset at the top of the list to adhere to the Material Design spec. + */ + protected class ListItemDecoration extends RecyclerView.ItemDecoration { + private final int[] ATTRS = new int[] { android.R.attr.listDivider }; + + private final int TOP_LIST_OFFSET = 8; + + private Drawable mDivider; + + public ListItemDecoration(Context c) { + final TypedArray a = c.obtainStyledAttributes(ATTRS); + mDivider = a.getDrawable(0); + a.recycle(); + } + + @Override + public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { + final int left = parent.getPaddingLeft(); + final int right = parent.getWidth() - parent.getPaddingRight(); + + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + final RecyclerView.LayoutParams params = + (RecyclerView.LayoutParams) child.getLayoutParams(); + final int top = child.getBottom() + params.bottomMargin; + final int bottom = top + mDivider.getIntrinsicHeight(); + mDivider.setBounds(left, top, right, bottom); + mDivider.draw(c); + } + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, + RecyclerView.State state) { + int top = parent.getChildAdapterPosition(view) == 0 ? TOP_LIST_OFFSET : 0; + outRect.set(0, top, 0, mDivider.getIntrinsicHeight()); + } + } + + protected abstract class ItemViewHolder extends RecyclerView.ViewHolder + implements View.OnClickListener, View.OnCreateContextMenuListener { + public ItemViewHolder(View v) { + super(v); + v.setOnClickListener(this); + v.setOnCreateContextMenuListener(this); + } + } + + @VisibleForTesting + protected static abstract class ItemAdapter extends RecyclerView.Adapter<ItemViewHolder> { + private final LayoutInflater inflater; + protected final Context context; + + public ItemAdapter(Context context/*, List<AbstractBean> items*/) { + this.context = context; + this.inflater = LayoutInflater.from(context); + } + } +} diff --git a/app/src/main/java/org/connectbot/HostListActivity.java b/app/src/main/java/org/connectbot/HostListActivity.java index 4dfeffd..41ba62b 100644 --- a/app/src/main/java/org/connectbot/HostListActivity.java +++ b/app/src/main/java/org/connectbot/HostListActivity.java @@ -17,15 +17,6 @@ package org.connectbot; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.support.annotation.VisibleForTesting; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; - import java.util.List; import org.connectbot.bean.HostBean; @@ -52,6 +43,9 @@ import android.os.Bundle; import android.os.IBinder; import android.preference.PreferenceManager; import android.support.annotation.StyleRes; +import android.support.annotation.VisibleForTesting; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.text.format.DateUtils; import android.util.Log; import android.view.ContextMenu; @@ -69,7 +63,7 @@ import android.widget.ImageView; import android.widget.Spinner; import android.widget.TextView; -public class HostListActivity extends AppCompatActivity implements OnHostStatusChangedListener { +public class HostListActivity extends AppCompatListActivity implements OnHostStatusChangedListener { public final static String TAG = "CB.HostListActivity"; public static final String DISCONNECT_ACTION = "org.connectbot.action.DISCONNECT"; @@ -81,10 +75,6 @@ public class HostListActivity extends AppCompatActivity implements OnHostStatusC private List<HostBean> hosts; protected LayoutInflater inflater = null; - private RecyclerView mHostListView; - private HostAdapter mAdapter; - private View mEmptyView; - protected boolean sortedByColor = false; private MenuItem sortcolor; @@ -182,10 +172,10 @@ public class HostListActivity extends AppCompatActivity implements OnHostStatusC super.onCreate(icicle); setContentView(R.layout.act_hostlist); - mHostListView = (RecyclerView) findViewById(R.id.list); - mHostListView.setHasFixedSize(true); - mHostListView.setLayoutManager(new LinearLayoutManager(this)); - mHostListView.addItemDecoration(new HostListItemDecoration(this)); + mListView = (RecyclerView) findViewById(R.id.list); + mListView.setHasFixedSize(true); + mListView.setLayoutManager(new LinearLayoutManager(this)); + mListView.addItemDecoration(new ListItemDecoration(this)); mEmptyView = findViewById(R.id.empty); @@ -222,7 +212,7 @@ public class HostListActivity extends AppCompatActivity implements OnHostStatusC this.sortedByColor = prefs.getBoolean(PreferenceConstants.SORT_BY_COLOR, false); - this.registerForContextMenu(mHostListView); + this.registerForContextMenu(mListView); quickconnect = (TextView) this.findViewById(R.id.front_quickconnect); quickconnect.setVisibility(makingShortcut ? View.GONE : View.VISIBLE); @@ -356,7 +346,6 @@ public class HostListActivity extends AppCompatActivity implements OnHostStatusC }).create().show(); } - /** * @return */ @@ -411,7 +400,7 @@ public class HostListActivity extends AppCompatActivity implements OnHostStatusC } mAdapter = new HostAdapter(this, hosts, bound); - mHostListView.setAdapter(mAdapter); + mListView.setAdapter(mAdapter); adjustViewVisibility(); } @@ -420,136 +409,122 @@ public class HostListActivity extends AppCompatActivity implements OnHostStatusC updateList(); } - /** - * If the host list is empty, hides the list and shows the empty message; otherwise, shows - * the list and hides the empty message. - */ - private void adjustViewVisibility() { - boolean isEmpty = mAdapter.getItemCount() == 0; - mHostListView.setVisibility(isEmpty ? View.GONE : View.VISIBLE); - mEmptyView.setVisibility(isEmpty ? View.VISIBLE : View.GONE); - } + private class HostViewHolder extends ItemViewHolder { + public final ImageView icon; + public final TextView nickname; + public final TextView caption; - @VisibleForTesting - protected class HostAdapter extends RecyclerView.Adapter<HostAdapter.ViewHolder> { - private final LayoutInflater inflater; - private final List<HostBean> hosts; - private final TerminalManager manager; - private final Context context; + public HostBean host; - public final static int STATE_UNKNOWN = 1, STATE_CONNECTED = 2, STATE_DISCONNECTED = 3; + public HostViewHolder(View v) { + super(v); - class ViewHolder extends RecyclerView.ViewHolder - implements View.OnClickListener, View.OnCreateContextMenuListener { - public final ImageView icon; - public final TextView nickname; - public final TextView caption; - public HostBean host; - - public ViewHolder(View v) { - super(v); - v.setOnClickListener(this); - v.setOnCreateContextMenuListener(this); - - icon = (ImageView) v.findViewById(android.R.id.icon); - nickname = (TextView) v.findViewById(android.R.id.text1); - caption = (TextView) v.findViewById(android.R.id.text2); - } + icon = (ImageView) v.findViewById(android.R.id.icon); + nickname = (TextView) v.findViewById(android.R.id.text1); + caption = (TextView) v.findViewById(android.R.id.text2); + } - @Override - public void onClick(View v) { - // launch off to console details - Uri uri = host.getUri(); + @Override + public void onClick(View v) { + // launch off to console details + Uri uri = host.getUri(); - Intent contents = new Intent(Intent.ACTION_VIEW, uri); - contents.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + Intent contents = new Intent(Intent.ACTION_VIEW, uri); + contents.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - if (makingShortcut) { - // create shortcut if requested - ShortcutIconResource icon = Intent.ShortcutIconResource.fromContext( - HostListActivity.this, R.drawable.icon); + if (makingShortcut) { + // create shortcut if requested + ShortcutIconResource icon = Intent.ShortcutIconResource.fromContext( + HostListActivity.this, R.drawable.icon); - Intent intent = new Intent(); - intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, contents); - intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, host.getNickname()); - intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon); + Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, contents); + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, host.getNickname()); + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon); - setResult(RESULT_OK, intent); - finish(); + setResult(RESULT_OK, intent); + finish(); - } else { - // otherwise just launch activity to show this host - contents.setClass(HostListActivity.this, ConsoleActivity.class); - HostListActivity.this.startActivity(contents); - } + } else { + // otherwise just launch activity to show this host + contents.setClass(HostListActivity.this, ConsoleActivity.class); + HostListActivity.this.startActivity(contents); } + } - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - menu.setHeaderTitle(host.getNickname()); - - // edit, disconnect, delete - MenuItem connect = menu.add(R.string.list_host_disconnect); - final TerminalBridge bridge = (bound == null) ? null : bound.getConnectedBridge(host); - connect.setEnabled(bridge != null); - connect.setOnMenuItemClickListener(new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - bridge.dispatchDisconnect(true); - return true; - } - }); - - MenuItem edit = menu.add(R.string.list_host_edit); - edit.setOnMenuItemClickListener(new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - Intent intent = new Intent(HostListActivity.this, HostEditorActivity.class); - intent.putExtra(Intent.EXTRA_TITLE, host.getId()); - HostListActivity.this.startActivityForResult(intent, REQUEST_EDIT); - return true; - } - }); - - MenuItem portForwards = menu.add(R.string.list_host_portforwards); - portForwards.setOnMenuItemClickListener(new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - Intent intent = new Intent(HostListActivity.this, PortForwardListActivity.class); - intent.putExtra(Intent.EXTRA_TITLE, host.getId()); - HostListActivity.this.startActivityForResult(intent, REQUEST_EDIT); - return true; - } - }); - if (!TransportFactory.canForwardPorts(host.getProtocol())) - portForwards.setEnabled(false); - - MenuItem delete = menu.add(R.string.list_host_delete); - delete.setOnMenuItemClickListener(new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - // prompt user to make sure they really want this - new AlertDialog.Builder(HostListActivity.this) - .setMessage(getString(R.string.delete_message, host.getNickname())) - .setPositiveButton(R.string.delete_pos, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - // make sure we disconnect - if (bridge != null) - bridge.dispatchDisconnect(true); - - hostdb.deleteHost(host); - updateList(); - } - }) - .setNegativeButton(R.string.delete_neg, null).create().show(); - - return true; - } - }); - } + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + menu.setHeaderTitle(host.getNickname()); + + // edit, disconnect, delete + MenuItem connect = menu.add(R.string.list_host_disconnect); + final TerminalBridge bridge = (bound == null) ? null : bound.getConnectedBridge(host); + connect.setEnabled(bridge != null); + connect.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + bridge.dispatchDisconnect(true); + return true; + } + }); + + MenuItem edit = menu.add(R.string.list_host_edit); + edit.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + Intent intent = new Intent(HostListActivity.this, HostEditorActivity.class); + intent.putExtra(Intent.EXTRA_TITLE, host.getId()); + HostListActivity.this.startActivityForResult(intent, REQUEST_EDIT); + return true; + } + }); + + MenuItem portForwards = menu.add(R.string.list_host_portforwards); + portForwards.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + Intent intent = new Intent(HostListActivity.this, PortForwardListActivity.class); + intent.putExtra(Intent.EXTRA_TITLE, host.getId()); + HostListActivity.this.startActivityForResult(intent, REQUEST_EDIT); + return true; + } + }); + if (!TransportFactory.canForwardPorts(host.getProtocol())) + portForwards.setEnabled(false); + + MenuItem delete = menu.add(R.string.list_host_delete); + delete.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // prompt user to make sure they really want this + new AlertDialog.Builder(HostListActivity.this) + .setMessage(getString(R.string.delete_message, host.getNickname())) + .setPositiveButton(R.string.delete_pos, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // make sure we disconnect + if (bridge != null) + bridge.dispatchDisconnect(true); + + hostdb.deleteHost(host); + updateList(); + } + }) + .setNegativeButton(R.string.delete_neg, null).create().show(); + + return true; + } + }); } + } + + @VisibleForTesting + private class HostAdapter extends ItemAdapter { + private final List<HostBean> hosts; + private final TerminalManager manager; + + public final static int STATE_UNKNOWN = 1, STATE_CONNECTED = 2, STATE_DISCONNECTED = 3; public HostAdapter(Context context, List<HostBean> hosts, TerminalManager manager) { - this.context = context; + super(context); + this.hosts = hosts; this.manager = manager; - this.inflater = LayoutInflater.from(context); } /** @@ -570,36 +545,38 @@ public class HostListActivity extends AppCompatActivity implements OnHostStatusC } @Override - public HostAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public HostViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_host, parent, false); - ViewHolder vh = new ViewHolder(v); + HostViewHolder vh = new HostViewHolder(v); return vh; } @Override - public void onBindViewHolder(ViewHolder holder, int position) { + public void onBindViewHolder(ItemViewHolder holder, int position) { + HostViewHolder hostHolder = (HostViewHolder) holder; + HostBean host = hosts.get(position); if (host == null) { // Well, something bad happened. We can't continue. Log.e("HostAdapter", "Host bean is null!"); - holder.nickname.setText("Error during lookup"); - holder.caption.setText("see 'adb logcat' for more"); + hostHolder.nickname.setText("Error during lookup"); + hostHolder.caption.setText("see 'adb logcat' for more"); } - holder.host = host; + hostHolder.host = host; - holder.nickname.setText(host.getNickname()); + hostHolder.nickname.setText(host.getNickname()); switch (this.getConnectedState(host)) { case STATE_UNKNOWN: - holder.icon.setImageState(new int[] { }, true); + hostHolder.icon.setImageState(new int[] { }, true); break; case STATE_CONNECTED: - holder.icon.setImageState(new int[] { android.R.attr.state_checked }, true); + hostHolder.icon.setImageState(new int[] { android.R.attr.state_checked }, true); break; case STATE_DISCONNECTED: - holder.icon.setImageState(new int[] { android.R.attr.state_expanded }, true); + hostHolder.icon.setImageState(new int[] { android.R.attr.state_expanded }, true); break; } @@ -619,19 +596,15 @@ public class HostListActivity extends AppCompatActivity implements OnHostStatusC chosenStyleSecondLine = R.style.ListItemSecondLineText; } - holder.nickname.setTextAppearance(context, chosenStyleFirstLine); - holder.caption.setTextAppearance(context, chosenStyleSecondLine); + hostHolder.nickname.setTextAppearance(context, chosenStyleFirstLine); + hostHolder.caption.setTextAppearance(context, chosenStyleSecondLine); CharSequence nice = context.getString(R.string.bind_never); if (host.getLastConnect() > 0) { nice = DateUtils.getRelativeTimeSpanString(host.getLastConnect() * 1000); } - holder.caption.setText(nice); - } - - public HostBean getItem(int position) { - return hosts.get(position); + hostHolder.caption.setText(nice); } @Override @@ -644,48 +617,4 @@ public class HostListActivity extends AppCompatActivity implements OnHostStatusC return hosts.size(); } } - - /** - * Item decorations for host list items, which adds a divider between items and leaves a - * small offset at the top of the list to adhere to the Material Design spec. - */ - private class HostListItemDecoration extends RecyclerView.ItemDecoration { - private final int[] ATTRS = new int[]{ - android.R.attr.listDivider - }; - - private final int TOP_LIST_OFFSET = 8; - - private Drawable mDivider; - - public HostListItemDecoration(Context c) { - final TypedArray a = c.obtainStyledAttributes(ATTRS); - mDivider = a.getDrawable(0); - a.recycle(); - } - - @Override - public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { - final int left = parent.getPaddingLeft(); - final int right = parent.getWidth() - parent.getPaddingRight(); - - final int childCount = parent.getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = parent.getChildAt(i); - final RecyclerView.LayoutParams params = - (RecyclerView.LayoutParams) child.getLayoutParams(); - final int top = child.getBottom() + params.bottomMargin; - final int bottom = top + mDivider.getIntrinsicHeight(); - mDivider.setBounds(left, top, right, bottom); - mDivider.draw(c); - } - } - - @Override - public void getItemOffsets(Rect outRect, View view, RecyclerView parent, - RecyclerView.State state) { - int top = parent.getChildAdapterPosition(view) == 0 ? TOP_LIST_OFFSET : 0; - outRect.set(0, top, 0, mDivider.getIntrinsicHeight()); - } - } } diff --git a/app/src/main/java/org/connectbot/PortForwardListActivity.java b/app/src/main/java/org/connectbot/PortForwardListActivity.java index 868c611..28b4331 100644 --- a/app/src/main/java/org/connectbot/PortForwardListActivity.java +++ b/app/src/main/java/org/connectbot/PortForwardListActivity.java @@ -26,7 +26,6 @@ import org.connectbot.service.TerminalManager; import org.connectbot.util.HostDatabase; import android.app.AlertDialog; -import android.app.ListActivity; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; @@ -39,23 +38,23 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.support.annotation.VisibleForTesting; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; import android.view.View; import android.view.ViewGroup; -import android.view.MenuItem.OnMenuItemClickListener; import android.widget.AdapterView; -import android.widget.ArrayAdapter; +import android.widget.AdapterView.OnItemSelectedListener; import android.widget.EditText; -import android.widget.ListView; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.AdapterView.OnItemSelectedListener; /** * List all portForwards for a particular host and provide a way for users to add more portForwards, @@ -63,7 +62,7 @@ import android.widget.AdapterView.OnItemSelectedListener; * * @author Kenny Root */ -public class PortForwardListActivity extends ListActivity { +public class PortForwardListActivity extends AppCompatListActivity { public final static String TAG = "CB.PortForwardListAct"; private static final int LISTENER_CYCLE_TIME = 500; @@ -104,6 +103,13 @@ public class PortForwardListActivity extends ListActivity { setContentView(R.layout.act_portforwardlist); + mListView = (RecyclerView) findViewById(R.id.list); + mListView.setHasFixedSize(true); + mListView.setLayoutManager(new LinearLayoutManager(this)); + mListView.addItemDecoration(new ListItemDecoration(this)); + + mEmptyView = findViewById(R.id.empty); + // connect with hosts database and populate list this.hostdb = HostDatabase.get(this); host = hostdb.findHostById(hostId); @@ -134,25 +140,7 @@ public class PortForwardListActivity extends ListActivity { this.updateList(); - this.registerForContextMenu(this.getListView()); - - this.getListView().setOnItemClickListener(new OnItemClickListener() { - public void onItemClick(AdapterView<?> adapter, View view, int position, long id) { - ListView lv = PortForwardListActivity.this.getListView(); - PortForwardBean pfb = (PortForwardBean) lv.getItemAtPosition(position); - - if (hostBridge != null) { - if (pfb.isEnabled()) - hostBridge.disablePortForward(pfb); - else { - if (!hostBridge.enablePortForward(pfb)) - Toast.makeText(PortForwardListActivity.this, getString(R.string.portforward_problem), Toast.LENGTH_LONG).show(); - } - - updateHandler.sendEmptyMessage(-1); - } - } - }); + this.registerForContextMenu(mListView); this.inflater = LayoutInflater.from(this); } @@ -200,18 +188,18 @@ public class PortForwardListActivity extends ListActivity { break; } - PortForwardBean pfb = new PortForwardBean( + PortForwardBean portForward = new PortForwardBean( host != null ? host.getId() : -1, nicknameEdit.getText().toString(), type, sourcePortEdit.getText().toString(), destEdit.getText().toString()); if (hostBridge != null) { - hostBridge.addPortForward(pfb); - hostBridge.enablePortForward(pfb); + hostBridge.addPortForward(portForward); + hostBridge.enablePortForward(portForward); } - if (host != null && !hostdb.savePortForward(pfb)) { + if (host != null && !hostdb.savePortForward(portForward)) { throw new SQLException("Could not save port forward"); } @@ -231,188 +219,217 @@ public class PortForwardListActivity extends ListActivity { return true; } - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - // Create menu to handle deleting and editing port forward - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - final PortForwardBean pfb = (PortForwardBean) this.getListView().getItemAtPosition(info.position); + protected Handler updateHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + PortForwardListActivity.this.updateList(); + } + }; - menu.setHeaderTitle(pfb.getNickname()); + protected void updateList() { + if (hostBridge != null) { + this.portForwards = hostBridge.getPortForwards(); + } else { + if (this.hostdb == null) return; + this.portForwards = this.hostdb.getPortForwardsForHost(host); + } - MenuItem edit = menu.add(R.string.portforward_edit); - edit.setOnMenuItemClickListener(new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - final View editTunnelView = inflater.inflate(R.layout.dia_portforward, null, false); - - final Spinner typeSpinner = (Spinner) editTunnelView.findViewById(R.id.portforward_type); - if (HostDatabase.PORTFORWARD_LOCAL.equals(pfb.getType())) - typeSpinner.setSelection(0); - else if (HostDatabase.PORTFORWARD_REMOTE.equals(pfb.getType())) - typeSpinner.setSelection(1); - else - typeSpinner.setSelection(2); - - final EditText nicknameEdit = (EditText) editTunnelView.findViewById(R.id.nickname); - nicknameEdit.setText(pfb.getNickname()); - - final EditText sourcePortEdit = (EditText) editTunnelView.findViewById(R.id.portforward_source); - sourcePortEdit.setText(String.valueOf(pfb.getSourcePort())); - - final EditText destEdit = (EditText) editTunnelView.findViewById(R.id.portforward_destination); - if (HostDatabase.PORTFORWARD_DYNAMIC5.equals(pfb.getType())) { - destEdit.setEnabled(false); - } else { - destEdit.setText(String.format("%s:%d", pfb.getDestAddr(), pfb.getDestPort())); - } + mAdapter = new PortForwardAdapter(this, portForwards); + mListView.setAdapter(mAdapter); + adjustViewVisibility(); + } - typeSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { - public void onItemSelected(AdapterView<?> value, View view, - int position, long id) { - destEdit.setEnabled(position != 2); - } - public void onNothingSelected(AdapterView<?> arg0) { - } - }); + private class PortForwardViewHolder extends ItemViewHolder { + public final TextView nickname; + public final TextView caption; - new AlertDialog.Builder(PortForwardListActivity.this) - .setView(editTunnelView) - .setPositiveButton(R.string.button_change, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - try { - if (hostBridge != null) - hostBridge.disablePortForward(pfb); + public PortForwardBean portForward; - pfb.setNickname(nicknameEdit.getText().toString()); + public PortForwardViewHolder(View v) { + super(v); - switch (typeSpinner.getSelectedItemPosition()) { - case 0: - pfb.setType(HostDatabase.PORTFORWARD_LOCAL); - break; - case 1: - pfb.setType(HostDatabase.PORTFORWARD_REMOTE); - break; - case 2: - pfb.setType(HostDatabase.PORTFORWARD_DYNAMIC5); - break; - } + nickname = (TextView) v.findViewById(android.R.id.text1); + caption = (TextView) v.findViewById(android.R.id.text2); + } - pfb.setSourcePort(Integer.parseInt(sourcePortEdit.getText().toString())); - pfb.setDest(destEdit.getText().toString()); + @Override + public void onClick(View v) { + if (hostBridge != null) { + if (portForward.isEnabled()) + hostBridge.disablePortForward(portForward); + else { + if (!hostBridge.enablePortForward(portForward)) + Toast.makeText(PortForwardListActivity.this, getString(R.string.portforward_problem), Toast.LENGTH_LONG).show(); + } - // Use the new settings for the existing connection. - if (hostBridge != null) - updateHandler.postDelayed(new Runnable() { - public void run() { - hostBridge.enablePortForward(pfb); - updateHandler.sendEmptyMessage(-1); - } - }, LISTENER_CYCLE_TIME); + updateHandler.sendEmptyMessage(-1); + } + } + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + // Create menu to handle deleting and editing port forward + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + + menu.setHeaderTitle(portForward.getNickname()); + + MenuItem edit = menu.add(R.string.portforward_edit); + edit.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + final View editTunnelView = inflater.inflate(R.layout.dia_portforward, null, false); + + final Spinner typeSpinner = (Spinner) editTunnelView.findViewById(R.id.portforward_type); + if (HostDatabase.PORTFORWARD_LOCAL.equals(portForward.getType())) + typeSpinner.setSelection(0); + else if (HostDatabase.PORTFORWARD_REMOTE.equals(portForward.getType())) + typeSpinner.setSelection(1); + else + typeSpinner.setSelection(2); + + final EditText nicknameEdit = (EditText) editTunnelView.findViewById(R.id.nickname); + nicknameEdit.setText(portForward.getNickname()); + + final EditText sourcePortEdit = (EditText) editTunnelView.findViewById(R.id.portforward_source); + sourcePortEdit.setText(String.valueOf(portForward.getSourcePort())); + + final EditText destEdit = (EditText) editTunnelView.findViewById(R.id.portforward_destination); + if (HostDatabase.PORTFORWARD_DYNAMIC5.equals(portForward.getType())) { + destEdit.setEnabled(false); + } else { + destEdit.setText(String.format("%s:%d", portForward.getDestAddr(), portForward.getDestPort())); + } - if (!hostdb.savePortForward(pfb)) { - throw new SQLException("Could not save port forward"); - } + typeSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { + public void onItemSelected(AdapterView<?> value, View view, + int position, long id) { + destEdit.setEnabled(position != 2); + } - updateHandler.sendEmptyMessage(-1); - } catch (Exception e) { - Log.e(TAG, "Could not update port forward", e); - // TODO Show failure dialog. - } + public void onNothingSelected(AdapterView<?> arg0) { } - }) - .setNegativeButton(android.R.string.cancel, null).create().show(); + }); + + new AlertDialog.Builder(PortForwardListActivity.this) + .setView(editTunnelView) + .setPositiveButton(R.string.button_change, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + try { + if (hostBridge != null) + hostBridge.disablePortForward(portForward); + + portForward.setNickname(nicknameEdit.getText().toString()); + + switch (typeSpinner.getSelectedItemPosition()) { + case 0: + portForward.setType(HostDatabase.PORTFORWARD_LOCAL); + break; + case 1: + portForward.setType(HostDatabase.PORTFORWARD_REMOTE); + break; + case 2: + portForward.setType(HostDatabase.PORTFORWARD_DYNAMIC5); + break; + } - return true; - } - }); + portForward.setSourcePort(Integer.parseInt(sourcePortEdit.getText().toString())); + portForward.setDest(destEdit.getText().toString()); - MenuItem delete = menu.add(R.string.portforward_delete); - delete.setOnMenuItemClickListener(new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - // prompt user to make sure they really want this - new AlertDialog.Builder(PortForwardListActivity.this) - .setMessage(getString(R.string.delete_message, pfb.getNickname())) - .setPositiveButton(R.string.delete_pos, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - try { - // Delete the port forward from the host if needed. - if (hostBridge != null) - hostBridge.removePortForward(pfb); + // Use the new settings for the existing connection. + if (hostBridge != null) + updateHandler.postDelayed(new Runnable() { + public void run() { + hostBridge.enablePortForward(portForward); + updateHandler.sendEmptyMessage(-1); + } + }, LISTENER_CYCLE_TIME); - hostdb.deletePortForward(pfb); - } catch (Exception e) { - Log.e(TAG, "Could not delete port forward", e); - } - updateHandler.sendEmptyMessage(-1); - } - }) - .setNegativeButton(R.string.delete_neg, null).create().show(); + if (!hostdb.savePortForward(portForward)) { + throw new SQLException("Could not save port forward"); + } - return true; - } - }); - } + updateHandler.sendEmptyMessage(-1); + } catch (Exception e) { + Log.e(TAG, "Could not update port forward", e); + // TODO Show failure dialog. + } + } + }) + .setNegativeButton(android.R.string.cancel, null).create().show(); - protected Handler updateHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - PortForwardListActivity.this.updateList(); - } - }; + return true; + } + }); + + MenuItem delete = menu.add(R.string.portforward_delete); + delete.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // prompt user to make sure they really want this + new AlertDialog.Builder(PortForwardListActivity.this) + .setMessage(getString(R.string.delete_message, portForward.getNickname())) + .setPositiveButton(R.string.delete_pos, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + try { + // Delete the port forward from the host if needed. + if (hostBridge != null) + hostBridge.removePortForward(portForward); + + hostdb.deletePortForward(portForward); + } catch (Exception e) { + Log.e(TAG, "Could not delete port forward", e); + } + + updateHandler.sendEmptyMessage(-1); + } + }) + .setNegativeButton(R.string.delete_neg, null).create().show(); - protected void updateList() { - if (hostBridge != null) { - this.portForwards = hostBridge.getPortForwards(); - } else { - if (this.hostdb == null) return; - this.portForwards = this.hostdb.getPortForwardsForHost(host); + return true; + } + }); } - - PortForwardAdapter adapter = new PortForwardAdapter(this, portForwards); - - this.setListAdapter(adapter); } - class PortForwardAdapter extends ArrayAdapter<PortForwardBean> { - class ViewHolder { - public TextView nickname; - public TextView caption; - } - - private List<PortForwardBean> portForwards; + @VisibleForTesting + private class PortForwardAdapter extends ItemAdapter { + private final List<PortForwardBean> portForwards; public PortForwardAdapter(Context context, List<PortForwardBean> portForwards) { - super(context, R.layout.item_portforward, portForwards); - + super(context); this.portForwards = portForwards; } @Override - public View getView(int position, View convertView, ViewGroup parent) { - ViewHolder holder; - - if (convertView == null) { - convertView = inflater.inflate(R.layout.item_portforward, null, false); - - holder = new ViewHolder(); - holder.nickname = (TextView) convertView.findViewById(android.R.id.text1); - holder.caption = (TextView) convertView.findViewById(android.R.id.text2); + public PortForwardViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_portforward, parent, false); + PortForwardViewHolder vh = new PortForwardViewHolder(v); + return vh; + } - convertView.setTag(holder); - } else - holder = (ViewHolder) convertView.getTag(); + @Override + public void onBindViewHolder(ItemViewHolder holder, int position) { + PortForwardViewHolder portForwardHolder = (PortForwardViewHolder) holder; + PortForwardBean portForward = portForwards.get(position); - PortForwardBean pfb = portForwards.get(position); - holder.nickname.setText(pfb.getNickname()); - holder.caption.setText(pfb.getDescription()); + portForwardHolder.portForward = portForward; + portForwardHolder.nickname.setText(portForward.getNickname()); + portForwardHolder.caption.setText(portForward.getDescription()); - if (hostBridge != null && !pfb.isEnabled()) { - holder.nickname.setPaintFlags(holder.nickname.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); - holder.caption.setPaintFlags(holder.caption.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + if (hostBridge != null && !portForward.isEnabled()) { + portForwardHolder.nickname.setPaintFlags(portForwardHolder.nickname.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + portForwardHolder.caption.setPaintFlags(portForwardHolder.caption.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); } + } - return convertView; + @Override + public long getItemId(int position) { + return portForwards.get(position).getId(); + } + + @Override + public int getItemCount() { + return portForwards.size(); } } } diff --git a/app/src/main/java/org/connectbot/PubkeyListActivity.java b/app/src/main/java/org/connectbot/PubkeyListActivity.java index 3aea1bb..d25411f 100644 --- a/app/src/main/java/org/connectbot/PubkeyListActivity.java +++ b/app/src/main/java/org/connectbot/PubkeyListActivity.java @@ -37,6 +37,10 @@ import org.connectbot.util.PubkeyDatabase; import org.connectbot.util.PubkeyUtils; import org.openintents.intents.FileManagerIntents; +import com.trilead.ssh2.crypto.Base64; +import com.trilead.ssh2.crypto.PEMDecoder; +import com.trilead.ssh2.crypto.PEMStructure; + import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.ComponentName; @@ -45,16 +49,11 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.ServiceConnection; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.IBinder; import android.support.annotation.VisibleForTesting; -import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.ClipboardManager; @@ -73,17 +72,13 @@ import android.widget.TableRow; import android.widget.TextView; import android.widget.Toast; -import com.trilead.ssh2.crypto.Base64; -import com.trilead.ssh2.crypto.PEMDecoder; -import com.trilead.ssh2.crypto.PEMStructure; - /** * List public keys in database by nickname and describe their properties. Allow users to import, * generate, rename, and delete key pairs. * * @author Kenny Root */ -public class PubkeyListActivity extends AppCompatActivity implements EventListener { +public class PubkeyListActivity extends AppCompatListActivity implements EventListener { public final static String TAG = "CB.PubkeyListActivity"; private static final int MAX_KEYFILE_SIZE = 8192; @@ -99,10 +94,6 @@ public class PubkeyListActivity extends AppCompatActivity implements EventListen protected LayoutInflater inflater = null; - private View mEmptyView; - private RecyclerView mPubkeyListView; - private PubkeyAdapter mAdapter; - protected TerminalManager bound = null; private MenuItem onstartToggle = null; @@ -143,14 +134,14 @@ public class PubkeyListActivity extends AppCompatActivity implements EventListen super.onCreate(icicle); setContentView(R.layout.act_pubkeylist); - mPubkeyListView = (RecyclerView) findViewById(R.id.list); - mPubkeyListView.setHasFixedSize(true); - mPubkeyListView.setLayoutManager(new LinearLayoutManager(this)); - mPubkeyListView.addItemDecoration(new PubkeyListItemDecoration(this)); + mListView = (RecyclerView) findViewById(R.id.list); + mListView.setHasFixedSize(true); + mListView.setLayoutManager(new LinearLayoutManager(this)); + mListView.addItemDecoration(new ListItemDecoration(this)); mEmptyView = findViewById(R.id.empty); - registerForContextMenu(mPubkeyListView); + registerForContextMenu(mListView); clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); @@ -278,28 +269,15 @@ public class PubkeyListActivity extends AppCompatActivity implements EventListen updateList(); } - - protected void updateList() { PubkeyDatabase pubkeyDb = PubkeyDatabase.get(PubkeyListActivity.this); pubkeys = pubkeyDb.allPubkeys(); mAdapter = new PubkeyAdapter(this, pubkeys); - mPubkeyListView.setAdapter(mAdapter); + mListView.setAdapter(mAdapter); adjustViewVisibility(); } - /** - * If the pubkey list is empty, hides the list and shows the empty message; otherwise, shows - * the list and hides the empty message. - */ - // TODO: this method is being duplicated but could be part of a superclass - private void adjustViewVisibility() { - boolean isEmpty = mAdapter.getItemCount() == 0; - mPubkeyListView.setVisibility(isEmpty ? View.GONE : View.VISIBLE); - mEmptyView.setVisibility(isEmpty ? View.VISIBLE : View.GONE); - } - @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); @@ -430,242 +408,237 @@ public class PubkeyListActivity extends AppCompatActivity implements EventListen .setNegativeButton(android.R.string.cancel, null).create().show(); } - @VisibleForTesting - protected class PubkeyAdapter extends RecyclerView.Adapter<PubkeyAdapter.ViewHolder> { - private final LayoutInflater inflater; - private final List<PubkeyBean> pubkeys; - private final Context context; - - // KLUDGE: duplication of HostListActivity? - class ViewHolder extends RecyclerView.ViewHolder - implements View.OnClickListener, View.OnCreateContextMenuListener { - public final ImageView icon; - public final TextView nickname; - public final TextView caption; - public PubkeyBean pubkey; - - public ViewHolder(View v) { - super(v); - v.setOnClickListener(this); - v.setOnCreateContextMenuListener(this); - - icon = (ImageView) v.findViewById(android.R.id.icon); - nickname = (TextView) v.findViewById(android.R.id.text1); - caption = (TextView) v.findViewById(android.R.id.text2); - } + private class PubkeyViewHolder extends ItemViewHolder { + public final ImageView icon; + public final TextView nickname; + public final TextView caption; - @Override - public void onClick(View v) { - boolean loaded = bound != null && bound.isKeyLoaded(pubkey.getNickname()); + public PubkeyBean pubkey; - // handle toggling key in-memory on/off - if (loaded) { - bound.removeKey(pubkey.getNickname()); - updateList(); - } else { - handleAddKey(pubkey); - } + public PubkeyViewHolder(View v) { + super(v); + + icon = (ImageView) v.findViewById(android.R.id.icon); + nickname = (TextView) v.findViewById(android.R.id.text1); + caption = (TextView) v.findViewById(android.R.id.text2); + } + + @Override + public void onClick(View v) { + boolean loaded = bound != null && bound.isKeyLoaded(pubkey.getNickname()); + + // handle toggling key in-memory on/off + if (loaded) { + bound.removeKey(pubkey.getNickname()); + updateList(); + } else { + handleAddKey(pubkey); } + } - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - // Create menu to handle deleting and editing pubkey - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - - menu.setHeaderTitle(pubkey.getNickname()); - - // TODO: option load/unload key from in-memory list - // prompt for password as needed for passworded keys - - // cant change password or clipboard imported keys - final boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType()); - final boolean loaded = bound != null && bound.isKeyLoaded(pubkey.getNickname()); - - MenuItem load = menu.add(loaded ? R.string.pubkey_memory_unload : R.string.pubkey_memory_load); - load.setOnMenuItemClickListener(new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - if (loaded) { - bound.removeKey(pubkey.getNickname()); - updateList(); - } else { - handleAddKey(pubkey); - //bound.addKey(nickname, trileadKey); - } - return true; - } - }); - - onstartToggle = menu.add(R.string.pubkey_load_on_start); - onstartToggle.setEnabled(!pubkey.isEncrypted()); - onstartToggle.setCheckable(true); - onstartToggle.setChecked(pubkey.isStartup()); - onstartToggle.setOnMenuItemClickListener(new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - // toggle onstart status - pubkey.setStartup(!pubkey.isStartup()); - PubkeyDatabase pubkeyDb = PubkeyDatabase.get(PubkeyListActivity.this); - pubkeyDb.savePubkey(pubkey); + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + // Create menu to handle deleting and editing pubkey + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + + menu.setHeaderTitle(pubkey.getNickname()); + + // TODO: option load/unload key from in-memory list + // prompt for password as needed for passworded keys + + // cant change password or clipboard imported keys + final boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType()); + final boolean loaded = bound != null && bound.isKeyLoaded(pubkey.getNickname()); + + MenuItem load = menu.add(loaded ? R.string.pubkey_memory_unload : R.string.pubkey_memory_load); + load.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + if (loaded) { + bound.removeKey(pubkey.getNickname()); updateList(); - return true; + } else { + handleAddKey(pubkey); + //bound.addKey(nickname, trileadKey); } - }); - - MenuItem copyPublicToClipboard = menu.add(R.string.pubkey_copy_public); - copyPublicToClipboard.setEnabled(!imported); - copyPublicToClipboard.setOnMenuItemClickListener(new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - try { - PublicKey pk = PubkeyUtils.decodePublic(pubkey.getPublicKey(), pubkey.getType()); - String openSSHPubkey = PubkeyUtils.convertToOpenSSHFormat(pk, pubkey.getNickname()); - - clipboard.setText(openSSHPubkey); - } catch (Exception e) { - e.printStackTrace(); - } - return true; + return true; + } + }); + + onstartToggle = menu.add(R.string.pubkey_load_on_start); + onstartToggle.setEnabled(!pubkey.isEncrypted()); + onstartToggle.setCheckable(true); + onstartToggle.setChecked(pubkey.isStartup()); + onstartToggle.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // toggle onstart status + pubkey.setStartup(!pubkey.isStartup()); + PubkeyDatabase pubkeyDb = PubkeyDatabase.get(PubkeyListActivity.this); + pubkeyDb.savePubkey(pubkey); + updateList(); + return true; + } + }); + + MenuItem copyPublicToClipboard = menu.add(R.string.pubkey_copy_public); + copyPublicToClipboard.setEnabled(!imported); + copyPublicToClipboard.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + try { + PublicKey pk = PubkeyUtils.decodePublic(pubkey.getPublicKey(), pubkey.getType()); + String openSSHPubkey = PubkeyUtils.convertToOpenSSHFormat(pk, pubkey.getNickname()); + + clipboard.setText(openSSHPubkey); + } catch (Exception e) { + e.printStackTrace(); } - }); - - MenuItem copyPrivateToClipboard = menu.add(R.string.pubkey_copy_private); - copyPrivateToClipboard.setEnabled(!pubkey.isEncrypted() || imported); - copyPrivateToClipboard.setOnMenuItemClickListener(new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - try { - String data = null; - - if (imported) - data = new String(pubkey.getPrivateKey()); - else { - PrivateKey pk = PubkeyUtils.decodePrivate(pubkey.getPrivateKey(), pubkey.getType()); - data = PubkeyUtils.exportPEM(pk, null); - } - - clipboard.setText(data); - } catch (Exception e) { - e.printStackTrace(); + return true; + } + }); + + MenuItem copyPrivateToClipboard = menu.add(R.string.pubkey_copy_private); + copyPrivateToClipboard.setEnabled(!pubkey.isEncrypted() || imported); + copyPrivateToClipboard.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + try { + String data = null; + + if (imported) + data = new String(pubkey.getPrivateKey()); + else { + PrivateKey pk = PubkeyUtils.decodePrivate(pubkey.getPrivateKey(), pubkey.getType()); + data = PubkeyUtils.exportPEM(pk, null); } - return true; + + clipboard.setText(data); + } catch (Exception e) { + e.printStackTrace(); } - }); - - MenuItem changePassword = menu.add(R.string.pubkey_change_password); - changePassword.setEnabled(!imported); - changePassword.setOnMenuItemClickListener(new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - final View changePasswordView = inflater.inflate(R.layout.dia_changepassword, null, false); - ((TableRow) changePasswordView.findViewById(R.id.old_password_prompt)) - .setVisibility(pubkey.isEncrypted() ? View.VISIBLE : View.GONE); - new AlertDialog.Builder(PubkeyListActivity.this) - .setView(changePasswordView) - .setPositiveButton(R.string.button_change, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - String oldPassword = ((EditText) changePasswordView.findViewById(R.id.old_password)).getText().toString(); - String password1 = ((EditText) changePasswordView.findViewById(R.id.password1)).getText().toString(); - String password2 = ((EditText) changePasswordView.findViewById(R.id.password2)).getText().toString(); - - if (!password1.equals(password2)) { - new AlertDialog.Builder(PubkeyListActivity.this) - .setMessage(R.string.alert_passwords_do_not_match_msg) - .setPositiveButton(android.R.string.ok, null) - .create().show(); - return; - } + return true; + } + }); + + MenuItem changePassword = menu.add(R.string.pubkey_change_password); + changePassword.setEnabled(!imported); + changePassword.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + final View changePasswordView = inflater.inflate(R.layout.dia_changepassword, null, false); + ((TableRow) changePasswordView.findViewById(R.id.old_password_prompt)) + .setVisibility(pubkey.isEncrypted() ? View.VISIBLE : View.GONE); + new AlertDialog.Builder(PubkeyListActivity.this) + .setView(changePasswordView) + .setPositiveButton(R.string.button_change, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + String oldPassword = ((EditText) changePasswordView.findViewById(R.id.old_password)).getText().toString(); + String password1 = ((EditText) changePasswordView.findViewById(R.id.password1)).getText().toString(); + String password2 = ((EditText) changePasswordView.findViewById(R.id.password2)).getText().toString(); + + if (!password1.equals(password2)) { + new AlertDialog.Builder(PubkeyListActivity.this) + .setMessage(R.string.alert_passwords_do_not_match_msg) + .setPositiveButton(android.R.string.ok, null) + .create().show(); + return; + } - try { - if (!pubkey.changePassword(oldPassword, password1)) - new AlertDialog.Builder(PubkeyListActivity.this) - .setMessage(R.string.alert_wrong_password_msg) - .setPositiveButton(android.R.string.ok, null) - .create().show(); - else { - PubkeyDatabase pubkeyDb = PubkeyDatabase.get(PubkeyListActivity.this); - pubkeyDb.savePubkey(pubkey); - updateList(); - } - } catch (Exception e) { - Log.e(TAG, "Could not change private key password", e); + try { + if (!pubkey.changePassword(oldPassword, password1)) new AlertDialog.Builder(PubkeyListActivity.this) - .setMessage(R.string.alert_key_corrupted_msg) + .setMessage(R.string.alert_wrong_password_msg) .setPositiveButton(android.R.string.ok, null) .create().show(); + else { + PubkeyDatabase pubkeyDb = PubkeyDatabase.get(PubkeyListActivity.this); + pubkeyDb.savePubkey(pubkey); + updateList(); } + } catch (Exception e) { + Log.e(TAG, "Could not change private key password", e); + new AlertDialog.Builder(PubkeyListActivity.this) + .setMessage(R.string.alert_key_corrupted_msg) + .setPositiveButton(android.R.string.ok, null) + .create().show(); } - }) - .setNegativeButton(android.R.string.cancel, null).create().show(); - - return true; - } - }); - - confirmUse = menu.add(R.string.pubkey_confirm_use); - confirmUse.setCheckable(true); - confirmUse.setChecked(pubkey.isConfirmUse()); - confirmUse.setOnMenuItemClickListener(new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - // toggle confirm use - pubkey.setConfirmUse(!pubkey.isConfirmUse()); - PubkeyDatabase pubkeyDb = PubkeyDatabase.get(PubkeyListActivity.this); - pubkeyDb.savePubkey(pubkey); - updateList(); - return true; - } - }); - - MenuItem delete = menu.add(R.string.pubkey_delete); - delete.setOnMenuItemClickListener(new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - // prompt user to make sure they really want this - new AlertDialog.Builder(PubkeyListActivity.this) - .setMessage(getString(R.string.delete_message, pubkey.getNickname())) - .setPositiveButton(R.string.delete_pos, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - - // dont forget to remove from in-memory - if (loaded) { - bound.removeKey(pubkey.getNickname()); - } + } + }) + .setNegativeButton(android.R.string.cancel, null).create().show(); - // delete from backend database and update gui - PubkeyDatabase pubkeyDb = PubkeyDatabase.get(PubkeyListActivity.this); - pubkeyDb.deletePubkey(pubkey); - updateList(); + return true; + } + }); + + confirmUse = menu.add(R.string.pubkey_confirm_use); + confirmUse.setCheckable(true); + confirmUse.setChecked(pubkey.isConfirmUse()); + confirmUse.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // toggle confirm use + pubkey.setConfirmUse(!pubkey.isConfirmUse()); + PubkeyDatabase pubkeyDb = PubkeyDatabase.get(PubkeyListActivity.this); + pubkeyDb.savePubkey(pubkey); + updateList(); + return true; + } + }); + + MenuItem delete = menu.add(R.string.pubkey_delete); + delete.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // prompt user to make sure they really want this + new AlertDialog.Builder(PubkeyListActivity.this) + .setMessage(getString(R.string.delete_message, pubkey.getNickname())) + .setPositiveButton(R.string.delete_pos, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + + // dont forget to remove from in-memory + if (loaded) { + bound.removeKey(pubkey.getNickname()); } - }) - .setNegativeButton(R.string.delete_neg, null).create().show(); - return true; - } - }); - } + // delete from backend database and update gui + PubkeyDatabase pubkeyDb = PubkeyDatabase.get(PubkeyListActivity.this); + pubkeyDb.deletePubkey(pubkey); + updateList(); + } + }) + .setNegativeButton(R.string.delete_neg, null).create().show(); + + return true; + } + }); } + } + + @VisibleForTesting + private class PubkeyAdapter extends ItemAdapter { + private final List<PubkeyBean> pubkeys; public PubkeyAdapter(Context context, List<PubkeyBean> pubkeys) { - this.context = context; + super(context); this.pubkeys = pubkeys; - this.inflater = LayoutInflater.from(context); } @Override - public PubkeyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public PubkeyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_pubkey, parent, false); - ViewHolder vh = new ViewHolder(v); + PubkeyViewHolder vh = new PubkeyViewHolder(v); return vh; } - @Override - public void onBindViewHolder(ViewHolder holder, int position) { + public void onBindViewHolder(ItemViewHolder holder, int position) { + PubkeyViewHolder pubkeyHolder = (PubkeyViewHolder) holder; + PubkeyBean pubkey = pubkeys.get(position); if (pubkey == null) { // Well, something bad happened. We can't continue. Log.e("PubkeyAdapter", "Pubkey bean is null!"); - holder.nickname.setText("Error during lookup"); - holder.caption.setText("see 'adb logcat' for more"); + pubkeyHolder.nickname.setText("Error during lookup"); + pubkeyHolder.caption.setText("see 'adb logcat' for more"); } - holder.pubkey = pubkey; + pubkeyHolder.pubkey = pubkey; - holder.nickname.setText(pubkey.getNickname()); + pubkeyHolder.nickname.setText(pubkey.getNickname()); boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType()); @@ -673,88 +646,39 @@ public class PubkeyListActivity extends AppCompatActivity implements EventListen try { PEMStructure struct = PEMDecoder.parsePEM(new String(pubkey.getPrivateKey()).toCharArray()); String type = (struct.pemType == PEMDecoder.PEM_RSA_PRIVATE_KEY) ? "RSA" : "DSA"; - holder.caption.setText(String.format("%s unknown-bit", type)); + pubkeyHolder.caption.setText(String.format("%s unknown-bit", type)); } catch (IOException e) { Log.e(TAG, "Error decoding IMPORTED public key at " + pubkey.getId(), e); } } else { try { - holder.caption.setText(pubkey.getDescription()); + pubkeyHolder.caption.setText(pubkey.getDescription()); } catch (Exception e) { Log.e(TAG, "Error decoding public key at " + pubkey.getId(), e); - holder.caption.setText(R.string.pubkey_unknown_format); + pubkeyHolder.caption.setText(R.string.pubkey_unknown_format); } } if (bound == null) { - holder.icon.setVisibility(View.GONE); + pubkeyHolder.icon.setVisibility(View.GONE); } else { - holder.icon.setVisibility(View.VISIBLE); + pubkeyHolder.icon.setVisibility(View.VISIBLE); if (bound.isKeyLoaded(pubkey.getNickname())) - holder.icon.setImageState(new int[] { android.R.attr.state_checked }, true); + pubkeyHolder.icon.setImageState(new int[] { android.R.attr.state_checked }, true); else - holder.icon.setImageState(new int[] { }, true); + pubkeyHolder.icon.setImageState(new int[] { }, true); } } - public PubkeyBean getItem(int position) { - return pubkeys.get(position); - } - - @Override - public long getItemId(int position) { - return pubkeys.get(position).getId(); - } - @Override public int getItemCount() { return pubkeys.size(); } - } - - /** - * Item decorations for pubkey list items, which adds a divider between items and leaves a - * small offset at the top of the list to adhere to the Material Design spec. - */ - // KLUDGE: duplication - private class PubkeyListItemDecoration extends RecyclerView.ItemDecoration { - private final int[] ATTRS = new int[]{ - android.R.attr.listDivider - }; - - private final int TOP_LIST_OFFSET = 8; - - private Drawable mDivider; - - public PubkeyListItemDecoration(Context c) { - final TypedArray a = c.obtainStyledAttributes(ATTRS); - mDivider = a.getDrawable(0); - a.recycle(); - } @Override - public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { - final int left = parent.getPaddingLeft(); - final int right = parent.getWidth() - parent.getPaddingRight(); - - final int childCount = parent.getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = parent.getChildAt(i); - final RecyclerView.LayoutParams params = - (RecyclerView.LayoutParams) child.getLayoutParams(); - final int top = child.getBottom() + params.bottomMargin; - final int bottom = top + mDivider.getIntrinsicHeight(); - mDivider.setBounds(left, top, right, bottom); - mDivider.draw(c); - } - } - - @Override - public void getItemOffsets(Rect outRect, View view, RecyclerView parent, - RecyclerView.State state) { - int top = parent.getChildAdapterPosition(view) == 0 ? TOP_LIST_OFFSET : 0; - outRect.set(0, top, 0, mDivider.getIntrinsicHeight()); + public long getItemId(int position) { + return pubkeys.get(position).getId(); } } } diff --git a/app/src/main/res/layout/act_portforwardlist.xml b/app/src/main/res/layout/act_portforwardlist.xml index 46393ae..a11bb1b 100644 --- a/app/src/main/res/layout/act_portforwardlist.xml +++ b/app/src/main/res/layout/act_portforwardlist.xml @@ -24,15 +24,15 @@ android:orientation="vertical" > - <ListView - android:id="@android:id/list" + <android.support.v7.widget.RecyclerView + android:id="@+id/list" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" /> <TextView - android:id="@android:id/empty" + android:id="@+id/empty" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center" |