aboutsummaryrefslogtreecommitdiffstats
path: root/app/src
diff options
context:
space:
mode:
authorKenny Root <kenny@the-b.org>2015-09-22 15:39:34 -0700
committerKenny Root <kenny@the-b.org>2015-09-22 15:39:34 -0700
commitad121b83ec0e1d4ec66439574014024c68d8562c (patch)
tree1fa8134cd51a94df40885d5b019eaf122a35c504 /app/src
parentea5c3e8b7f1926af45921d35d79fb6dca63b5fd3 (diff)
parent8cf0f9eed006e3eeab2f59814de6c7c7aca5e1a8 (diff)
downloadconnectbot-ad121b83ec0e1d4ec66439574014024c68d8562c.tar.gz
connectbot-ad121b83ec0e1d4ec66439574014024c68d8562c.tar.bz2
connectbot-ad121b83ec0e1d4ec66439574014024c68d8562c.zip
Merge pull request #230 from rhansby/convert-activities-to-appcompat
Convert Activities to AppCompat
Diffstat (limited to 'app/src')
-rw-r--r--app/src/main/java/org/connectbot/AppCompatListActivity.java108
-rw-r--r--app/src/main/java/org/connectbot/AppCompatPreferenceActivity.java134
-rw-r--r--app/src/main/java/org/connectbot/ColorsActivity.java4
-rw-r--r--app/src/main/java/org/connectbot/EulaActivity.java4
-rw-r--r--app/src/main/java/org/connectbot/HelpActivity.java4
-rw-r--r--app/src/main/java/org/connectbot/HelpTopicActivity.java4
-rw-r--r--app/src/main/java/org/connectbot/HostEditorActivity.java5
-rw-r--r--app/src/main/java/org/connectbot/HostListActivity.java463
-rw-r--r--app/src/main/java/org/connectbot/PortForwardListActivity.java375
-rw-r--r--app/src/main/java/org/connectbot/PubkeyListActivity.java478
-rw-r--r--app/src/main/java/org/connectbot/SettingsActivity.java4
-rw-r--r--app/src/main/res/layout/act_portforwardlist.xml6
-rw-r--r--app/src/main/res/layout/act_pubkeylist.xml9
-rw-r--r--app/src/main/res/layout/item_pubkey.xml2
14 files changed, 904 insertions, 696 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/AppCompatPreferenceActivity.java b/app/src/main/java/org/connectbot/AppCompatPreferenceActivity.java
new file mode 100644
index 0000000..da623ae
--- /dev/null
+++ b/app/src/main/java/org/connectbot/AppCompatPreferenceActivity.java
@@ -0,0 +1,134 @@
+/*
+ * ConnectBot: simple, powerful, open-source SSH client for Android
+ * Copyright 2007 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.res.Configuration;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.Nullable;
+import android.support.v4.app.NavUtils;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatDelegate;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
+ private AppCompatDelegate mDelegate;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ getDelegate().installViewFactory();
+ getDelegate().onCreate(savedInstanceState);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ getDelegate().onPostCreate(savedInstanceState);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ public ActionBar getSupportActionBar() {
+ return getDelegate().getSupportActionBar();
+ }
+
+ public void setSupportActionBar(@Nullable Toolbar toolbar) {
+ getDelegate().setSupportActionBar(toolbar);
+ }
+
+ @Override
+ public MenuInflater getMenuInflater() {
+ return getDelegate().getMenuInflater();
+ }
+
+ @Override
+ public void setContentView(@LayoutRes int layoutResID) {
+ getDelegate().setContentView(layoutResID);
+ }
+
+ @Override
+ public void setContentView(View view) {
+ getDelegate().setContentView(view);
+ }
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ getDelegate().setContentView(view, params);
+ }
+
+ @Override
+ public void addContentView(View view, ViewGroup.LayoutParams params) {
+ getDelegate().addContentView(view, params);
+ }
+
+ @Override
+ protected void onPostResume() {
+ super.onPostResume();
+ getDelegate().onPostResume();
+ }
+
+ @Override
+ protected void onTitleChanged(CharSequence title, int color) {
+ super.onTitleChanged(title, color);
+ getDelegate().setTitle(title);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ getDelegate().onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ getDelegate().onStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ getDelegate().onDestroy();
+ }
+
+ public void invalidateOptionsMenu() {
+ getDelegate().invalidateOptionsMenu();
+ }
+
+ private AppCompatDelegate getDelegate() {
+ if (mDelegate == null) {
+ mDelegate = AppCompatDelegate.create(this, null);
+ }
+ return mDelegate;
+ }
+}
diff --git a/app/src/main/java/org/connectbot/ColorsActivity.java b/app/src/main/java/org/connectbot/ColorsActivity.java
index 1ec1fb0..b3b911a 100644
--- a/app/src/main/java/org/connectbot/ColorsActivity.java
+++ b/app/src/main/java/org/connectbot/ColorsActivity.java
@@ -25,11 +25,11 @@ import org.connectbot.util.HostDatabase;
import org.connectbot.util.UberColorPickerDialog;
import org.connectbot.util.UberColorPickerDialog.OnColorChangedListener;
-import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
import android.view.Menu;
import android.view.MenuItem;
@@ -47,7 +47,7 @@ import android.widget.AdapterView.OnItemSelectedListener;
* @author Kenny Root
*
*/
-public class ColorsActivity extends Activity implements OnItemClickListener, OnColorChangedListener, OnItemSelectedListener {
+public class ColorsActivity extends AppCompatActivity implements OnItemClickListener, OnColorChangedListener, OnItemSelectedListener {
private GridView mColorGrid;
private Spinner mFgSpinner;
private Spinner mBgSpinner;
diff --git a/app/src/main/java/org/connectbot/EulaActivity.java b/app/src/main/java/org/connectbot/EulaActivity.java
index 58caa30..57689ed 100644
--- a/app/src/main/java/org/connectbot/EulaActivity.java
+++ b/app/src/main/java/org/connectbot/EulaActivity.java
@@ -17,10 +17,10 @@
package org.connectbot;
-import android.app.Activity;
import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
-public class EulaActivity extends Activity {
+public class EulaActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
diff --git a/app/src/main/java/org/connectbot/HelpActivity.java b/app/src/main/java/org/connectbot/HelpActivity.java
index a46740a..3df8b2f 100644
--- a/app/src/main/java/org/connectbot/HelpActivity.java
+++ b/app/src/main/java/org/connectbot/HelpActivity.java
@@ -19,11 +19,11 @@ package org.connectbot;
import java.io.IOException;
-import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.content.res.AssetManager;
import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -35,7 +35,7 @@ import android.widget.LinearLayout;
* @author Kenny Root
*
*/
-public class HelpActivity extends Activity {
+public class HelpActivity extends AppCompatActivity {
public final static String TAG = "CB.HelpActivity";
public final static String HELPDIR = "help";
diff --git a/app/src/main/java/org/connectbot/HelpTopicActivity.java b/app/src/main/java/org/connectbot/HelpTopicActivity.java
index f7ab6a3..9f5573a 100644
--- a/app/src/main/java/org/connectbot/HelpTopicActivity.java
+++ b/app/src/main/java/org/connectbot/HelpTopicActivity.java
@@ -19,15 +19,15 @@ package org.connectbot;
import org.connectbot.util.HelpTopicView;
-import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
/**
* @author Kenny Root
*
*/
-public class HelpTopicActivity extends Activity {
+public class HelpTopicActivity extends AppCompatActivity {
public final static String TAG = "CB.HelpActivity";
@Override
diff --git a/app/src/main/java/org/connectbot/HostEditorActivity.java b/app/src/main/java/org/connectbot/HostEditorActivity.java
index f9dee45..e10d4be 100644
--- a/app/src/main/java/org/connectbot/HostEditorActivity.java
+++ b/app/src/main/java/org/connectbot/HostEditorActivity.java
@@ -23,8 +23,8 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.Map.Entry;
+import java.util.Set;
import org.connectbot.bean.HostBean;
import org.connectbot.service.TerminalBridge;
@@ -46,10 +46,9 @@ import android.os.IBinder;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.util.Log;
-public class HostEditorActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener {
+public class HostEditorActivity extends AppCompatPreferenceActivity implements OnSharedPreferenceChangeListener {
public class CursorPreferenceHack implements SharedPreferences {
protected final String table;
protected final long id;
diff --git a/app/src/main/java/org/connectbot/HostListActivity.java b/app/src/main/java/org/connectbot/HostListActivity.java
index dc1a5bc..fe5207a 100644
--- a/app/src/main/java/org/connectbot/HostListActivity.java
+++ b/app/src/main/java/org/connectbot/HostListActivity.java
@@ -17,16 +17,6 @@
package org.connectbot;
-import android.app.Activity;
-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;
@@ -38,6 +28,7 @@ import org.connectbot.transport.TransportFactory;
import org.connectbot.util.HostDatabase;
import org.connectbot.util.PreferenceConstants;
+import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ComponentName;
@@ -54,8 +45,11 @@ 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.design.widget.FloatingActionButton;
import android.support.v4.app.DialogFragment;
+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;
@@ -74,7 +68,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";
@@ -86,10 +80,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;
@@ -184,10 +174,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);
@@ -224,7 +214,7 @@ public class HostListActivity extends AppCompatActivity implements OnHostStatusC
this.sortedByColor = prefs.getBoolean(PreferenceConstants.SORT_BY_COLOR, false);
- this.registerForContextMenu(mHostListView);
+ this.registerForContextMenu(mListView);
FloatingActionButton addHostButton =
(FloatingActionButton) findViewById(R.id.add_host_button);
@@ -338,7 +328,6 @@ public class HostListActivity extends AppCompatActivity implements OnHostStatusC
}).create().show();
}
-
/**
* @return
*/
@@ -379,7 +368,7 @@ public class HostListActivity extends AppCompatActivity implements OnHostStatusC
}
mAdapter = new HostAdapter(this, hosts, bound);
- mHostListView.setAdapter(mAdapter);
+ mListView.setAdapter(mAdapter);
adjustViewVisibility();
}
@@ -388,136 +377,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;
- }
- });
+ @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 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();
+ 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;
- }
- });
- }
+ 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);
}
/**
@@ -538,36 +513,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;
}
@@ -587,19 +564,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
@@ -613,140 +586,96 @@ public class HostListActivity extends AppCompatActivity implements OnHostStatusC
}
}
- /**
- * 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 static class AddHostDialogFragment extends DialogFragment {
+ private TextView mAddressField;
+ private Spinner mSpinner;
- public HostListItemDecoration(Context c) {
- final TypedArray a = c.obtainStyledAttributes(ATTRS);
- mDivider = a.getDrawable(0);
- a.recycle();
- }
+ HostListActivity mListener;
- @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 onAttach(Activity activity) {
+ super.onAttach(activity);
+ mListener = (HostListActivity) activity;
}
- }
- @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());
- }
- }
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ LayoutInflater inflater = getActivity().getLayoutInflater();
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- public static class AddHostDialogFragment extends DialogFragment {
- private TextView mAddressField;
- private Spinner mSpinner;
+ View addHostDialog = inflater.inflate(R.layout.dia_add_host, null);
+ builder.setView(addHostDialog)
+ .setPositiveButton(R.string.button_add, null)
+ .setNegativeButton(R.string.button_cancel, null);
+ AlertDialog dialog = builder.create();
- HostListActivity mListener;
+ mAddressField = (TextView) addHostDialog.findViewById(R.id.front_quickconnect);
+ mAddressField.setOnKeyListener(new OnKeyListener() {
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- mListener = (HostListActivity) activity;
- }
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- LayoutInflater inflater = getActivity().getLayoutInflater();
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ if (event.getAction() == KeyEvent.ACTION_UP) return false;
+ if (keyCode != KeyEvent.KEYCODE_ENTER) return false;
- View addHostDialog = inflater.inflate(R.layout.dia_add_host, null);
- builder.setView(addHostDialog)
- .setPositiveButton(R.string.button_add, null)
- .setNegativeButton(R.string.button_cancel, null);
- AlertDialog dialog = builder.create();
+ processNewUriEntered();
+ return true;
+ }
+ });
- mAddressField = (TextView) addHostDialog.findViewById(R.id.front_quickconnect);
- mAddressField.setOnKeyListener(new OnKeyListener() {
+ mSpinner = (Spinner) addHostDialog.findViewById(R.id.transport_selection);
+ ArrayAdapter<String> transportSelection = new ArrayAdapter<String>(getActivity(),
+ android.R.layout.simple_spinner_item, TransportFactory.getTransportNames());
+ transportSelection.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ public void onItemSelected(AdapterView<?> arg0, View view, int position, long id) {
+ String formatHint = TransportFactory.getFormatHint(
+ (String) mSpinner.getSelectedItem(),
+ getActivity());
+ mAddressField.setHint(formatHint);
+ mAddressField.setError(null);
+ mAddressField.requestFocus();
+ }
- public boolean onKey(View v, int keyCode, KeyEvent event) {
+ public void onNothingSelected(AdapterView<?> arg0) {
+ }
+ });
+ mSpinner.setAdapter(transportSelection);
- if (event.getAction() == KeyEvent.ACTION_UP) return false;
- if (keyCode != KeyEvent.KEYCODE_ENTER) return false;
+ return dialog;
+ }
- processNewUriEntered();
- return true;
- }
- });
+ @Override
+ public void onResume()
+ {
+ super.onResume();
+ final AlertDialog alertDialog = (AlertDialog) getDialog();
+ Button addButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ addButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ processNewUriEntered();
+ }
+ });
+ }
- mSpinner = (Spinner) addHostDialog.findViewById(R.id.transport_selection);
- ArrayAdapter<String> transportSelection = new ArrayAdapter<String>(getActivity(),
- android.R.layout.simple_spinner_item, TransportFactory.getTransportNames());
- transportSelection.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
- public void onItemSelected(AdapterView<?> arg0, View view, int position, long id) {
- String formatHint = TransportFactory.getFormatHint(
- (String) mSpinner.getSelectedItem(),
- getActivity());
- mAddressField.setHint(formatHint);
- mAddressField.setError(null);
+ /**
+ * Processes the URI that has been entered. If it is a valid URI, adds that host
+ * and starts ConsoleActivity; otherwise, shows an error in the address field.
+ */
+ private void processNewUriEntered() {
+ Uri uri = TransportFactory.getUri((String) mSpinner
+ .getSelectedItem(), mAddressField.getText().toString());
+ if (uri == null) {
+ mAddressField.setError(getString(R.string.list_format_error,
+ TransportFactory.getFormatHint(
+ (String) mSpinner.getSelectedItem(),
+ getActivity())));
mAddressField.requestFocus();
+ return;
}
- public void onNothingSelected(AdapterView<?> arg0) {
- }
- });
- mSpinner.setAdapter(transportSelection);
-
- return dialog;
- }
-
- @Override
- public void onResume()
- {
- super.onResume();
- final AlertDialog alertDialog = (AlertDialog) getDialog();
- Button addButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
- addButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- processNewUriEntered();
- }
- });
- }
-
- /**
- * Processes the URI that has been entered. If it is a valid URI, adds that host
- * and starts ConsoleActivity; otherwise, shows an error in the address field.
- */
- private void processNewUriEntered() {
- Uri uri = TransportFactory.getUri((String) mSpinner
- .getSelectedItem(), mAddressField.getText().toString());
- if (uri == null) {
- mAddressField.setError(getString(R.string.list_format_error,
- TransportFactory.getFormatHint(
- (String) mSpinner.getSelectedItem(),
- getActivity())));
- mAddressField.requestFocus();
- return;
+ mListener.startConsoleActivity(uri);
+ getDialog().dismiss();
}
-
- mListener.startConsoleActivity(uri);
- getDialog().dismiss();
}
- }
}
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 d8a8e70..d25411f 100644
--- a/app/src/main/java/org/connectbot/PubkeyListActivity.java
+++ b/app/src/main/java/org/connectbot/PubkeyListActivity.java
@@ -37,8 +37,11 @@ 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.app.ListActivity;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
@@ -50,6 +53,9 @@ 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.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
import android.text.ClipboardManager;
import android.util.Log;
import android.view.ContextMenu;
@@ -60,25 +66,19 @@ import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageView;
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 ListActivity implements EventListener {
+public class PubkeyListActivity extends AppCompatListActivity implements EventListener {
public final static String TAG = "CB.PubkeyListActivity";
private static final int MAX_KEYFILE_SIZE = 8192;
@@ -134,23 +134,14 @@ public class PubkeyListActivity extends ListActivity implements EventListener {
super.onCreate(icicle);
setContentView(R.layout.act_pubkeylist);
- registerForContextMenu(getListView());
+ mListView = (RecyclerView) findViewById(R.id.list);
+ mListView.setHasFixedSize(true);
+ mListView.setLayoutManager(new LinearLayoutManager(this));
+ mListView.addItemDecoration(new ListItemDecoration(this));
- getListView().setOnItemClickListener(new OnItemClickListener() {
- public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
- PubkeyBean pubkey = (PubkeyBean) getListView().getItemAtPosition(position);
- boolean loaded = bound != null && bound.isKeyLoaded(pubkey.getNickname());
+ mEmptyView = findViewById(R.id.empty);
- // handle toggling key in-memory on/off
- if (loaded) {
- bound.removeKey(pubkey.getNickname());
- updateList();
- } else {
- handleAddKey(pubkey);
- }
-
- }
- });
+ registerForContextMenu(mListView);
clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
@@ -278,185 +269,13 @@ public class PubkeyListActivity extends ListActivity implements EventListener {
updateList();
}
- @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;
- final PubkeyBean pubkey = (PubkeyBean) getListView().getItemAtPosition(info.position);
-
- 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);
- 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();
- }
- 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);
- }
-
- clipboard.setText(data);
- } catch (Exception e) {
- e.printStackTrace();
- }
- 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);
- 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());
- }
-
- // 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;
- }
- });
-
- }
-
protected void updateList() {
PubkeyDatabase pubkeyDb = PubkeyDatabase.get(PubkeyListActivity.this);
pubkeys = pubkeyDb.allPubkeys();
- PubkeyAdapter adapter = new PubkeyAdapter(this, pubkeys);
- this.setListAdapter(adapter);
+ mAdapter = new PubkeyAdapter(this, pubkeys);
+ mListView.setAdapter(mAdapter);
+ adjustViewVisibility();
}
@Override
@@ -589,40 +408,237 @@ public class PubkeyListActivity extends ListActivity implements EventListener {
.setNegativeButton(android.R.string.cancel, null).create().show();
}
- class PubkeyAdapter extends ArrayAdapter<PubkeyBean> {
- private List<PubkeyBean> pubkeys;
+ private class PubkeyViewHolder extends ItemViewHolder {
+ public final ImageView icon;
+ public final TextView nickname;
+ public final TextView caption;
+
+ public PubkeyBean pubkey;
+
+ public PubkeyViewHolder(View v) {
+ super(v);
- class ViewHolder {
- public TextView nickname;
- public TextView caption;
- public ImageView icon;
+ icon = (ImageView) v.findViewById(android.R.id.icon);
+ nickname = (TextView) v.findViewById(android.R.id.text1);
+ caption = (TextView) v.findViewById(android.R.id.text2);
}
- public PubkeyAdapter(Context context, List<PubkeyBean> pubkeys) {
- super(context, R.layout.item_pubkey, pubkeys);
+ @Override
+ public void onClick(View v) {
+ boolean loaded = bound != null && bound.isKeyLoaded(pubkey.getNickname());
- this.pubkeys = pubkeys;
+ // handle toggling key in-memory on/off
+ if (loaded) {
+ bound.removeKey(pubkey.getNickname());
+ updateList();
+ } else {
+ handleAddKey(pubkey);
+ }
}
@Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ViewHolder holder;
+ 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);
+ updateList();
+ return true;
+ }
+ });
- if (convertView == null) {
- convertView = inflater.inflate(R.layout.item_pubkey, null, false);
+ 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());
- holder = new ViewHolder();
+ clipboard.setText(openSSHPubkey);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+ });
- holder.nickname = (TextView) convertView.findViewById(android.R.id.text1);
- holder.caption = (TextView) convertView.findViewById(android.R.id.text2);
- holder.icon = (ImageView) convertView.findViewById(android.R.id.icon1);
+ 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;
- convertView.setTag(holder);
- } else
- holder = (ViewHolder) convertView.getTag();
+ 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 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);
+ 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());
+ }
+
+ // 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) {
+ super(context);
+ this.pubkeys = pubkeys;
+ }
+
+ @Override
+ public PubkeyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View v = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_pubkey, parent, false);
+ PubkeyViewHolder vh = new PubkeyViewHolder(v);
+ return vh;
+ }
+
+ public void onBindViewHolder(ItemViewHolder holder, int position) {
+ PubkeyViewHolder pubkeyHolder = (PubkeyViewHolder) holder;
PubkeyBean pubkey = pubkeys.get(position);
- holder.nickname.setText(pubkey.getNickname());
+ if (pubkey == null) {
+ // Well, something bad happened. We can't continue.
+ Log.e("PubkeyAdapter", "Pubkey bean is null!");
+
+ pubkeyHolder.nickname.setText("Error during lookup");
+ pubkeyHolder.caption.setText("see 'adb logcat' for more");
+ }
+ pubkeyHolder.pubkey = pubkey;
+
+ pubkeyHolder.nickname.setText(pubkey.getNickname());
boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType());
@@ -630,31 +646,39 @@ public class PubkeyListActivity extends ListActivity implements EventListener {
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);
}
+ }
- return convertView;
+ @Override
+ public int getItemCount() {
+ return pubkeys.size();
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return pubkeys.get(position).getId();
}
}
}
diff --git a/app/src/main/java/org/connectbot/SettingsActivity.java b/app/src/main/java/org/connectbot/SettingsActivity.java
index 153f4ba..c04a32e 100644
--- a/app/src/main/java/org/connectbot/SettingsActivity.java
+++ b/app/src/main/java/org/connectbot/SettingsActivity.java
@@ -19,11 +19,10 @@ package org.connectbot;
import android.content.SharedPreferences;
import android.os.Bundle;
-import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.util.Log;
-public class SettingsActivity extends PreferenceActivity {
+public class SettingsActivity extends AppCompatPreferenceActivity {
private static final String TAG = "CB.Settings";
@Override
@@ -47,7 +46,6 @@ public class SettingsActivity extends PreferenceActivity {
}
// TODO: add parse checking here to make sure we have integer value for scrollback
-
}
}
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"
diff --git a/app/src/main/res/layout/act_pubkeylist.xml b/app/src/main/res/layout/act_pubkeylist.xml
index 0737d05..62b13eb 100644
--- a/app/src/main/res/layout/act_pubkeylist.xml
+++ b/app/src/main/res/layout/act_pubkeylist.xml
@@ -24,15 +24,14 @@
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"
+ android:layout_height="fill_parent"
/>
<TextView
- android:id="@android:id/empty"
+ android:id="@+id/empty"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
diff --git a/app/src/main/res/layout/item_pubkey.xml b/app/src/main/res/layout/item_pubkey.xml
index 2dce26b..6f63af9 100644
--- a/app/src/main/res/layout/item_pubkey.xml
+++ b/app/src/main/res/layout/item_pubkey.xml
@@ -26,7 +26,7 @@
android:padding="10dip">
<ImageView
- android:id="@android:id/icon1"
+ android:id="@android:id/icon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentEnd="true"