Mercurial > 510Connectbot
diff src/com/five_ten_sg/connectbot/HostListActivity.java @ 0:0ce5cc452d02
initial version
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Thu, 22 May 2014 10:41:19 -0700 |
parents | |
children | 75d86b7fd3f0 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/five_ten_sg/connectbot/HostListActivity.java Thu May 22 10:41:19 2014 -0700 @@ -0,0 +1,541 @@ +/* + * 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 com.five_ten_sg.connectbot; + +import java.util.List; + +import com.five_ten_sg.connectbot.bean.HostBean; +import com.five_ten_sg.connectbot.service.TerminalBridge; +import com.five_ten_sg.connectbot.service.TerminalManager; +import com.five_ten_sg.connectbot.transport.TransportFactory; +import com.five_ten_sg.connectbot.util.HostDatabase; +import com.five_ten_sg.connectbot.util.PreferenceConstants; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.Intent.ShortcutIconResource; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.content.res.ColorStateList; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.ContextMenu; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.View; +import android.view.View.OnKeyListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.Spinner; +import android.widget.TextView; + +public class HostListActivity extends ListActivity { + public final static int REQUEST_EDIT = 1; + + public final static int REQUEST_EULA = 2; + + protected TerminalManager bound = null; + + protected HostDatabase hostdb; + private List<HostBean> hosts; + protected LayoutInflater inflater = null; + + protected boolean sortedByColor = false; + + private MenuItem sortcolor; + + private MenuItem sortlast; + + private Spinner transportSpinner; + private TextView quickconnect; + + private SharedPreferences prefs = null; + + protected boolean makingShortcut = false; + + protected Handler updateHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + HostListActivity.this.updateList(); + } + }; + + private ServiceConnection connection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + bound = ((TerminalManager.TerminalBinder) service).getService(); + // update our listview binder to find the service + HostListActivity.this.updateList(); + } + public void onServiceDisconnected(ComponentName className) { + bound = null; + HostListActivity.this.updateList(); + } + }; + + @Override + public void onStart() { + super.onStart(); + // start the terminal manager service + this.bindService(new Intent(this, TerminalManager.class), connection, Context.BIND_AUTO_CREATE); + + if (this.hostdb == null) + this.hostdb = new HostDatabase(this); + } + + @Override + public void onStop() { + super.onStop(); + this.unbindService(connection); + + if (this.hostdb != null) { + this.hostdb.close(); + this.hostdb = null; + } + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_EULA) { + if (resultCode == Activity.RESULT_OK) { + // yay they agreed, so store that info + Editor edit = prefs.edit(); + edit.putBoolean(PreferenceConstants.EULA, true); + edit.commit(); + } + else { + // user didnt agree, so close + this.finish(); + } + } + else if (requestCode == REQUEST_EDIT) { + this.updateList(); + } + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.act_hostlist); + this.setTitle(String.format("%s: %s", + getResources().getText(R.string.app_name), + getResources().getText(R.string.title_hosts_list))); + this.prefs = PreferenceManager.getDefaultSharedPreferences(this); + + // detect HTC Dream and apply special preferences + if (Build.MANUFACTURER.equals("HTC") && Build.DEVICE.equals("dream")) { + if (!prefs.contains(PreferenceConstants.SHIFT_FKEYS) && + !prefs.contains(PreferenceConstants.CTRL_FKEYS)) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(PreferenceConstants.SHIFT_FKEYS, true); + editor.putBoolean(PreferenceConstants.CTRL_FKEYS, true); + editor.commit(); + } + } + + // check for eula agreement + boolean agreed = prefs.getBoolean(PreferenceConstants.EULA, false); + + if (!agreed) { + this.startActivityForResult(new Intent(this, WizardActivity.class), REQUEST_EULA); + } + + this.makingShortcut = Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction()) + || Intent.ACTION_PICK.equals(getIntent().getAction()); + // connect with hosts database and populate list + this.hostdb = new HostDatabase(this); + ListView list = this.getListView(); + this.sortedByColor = prefs.getBoolean(PreferenceConstants.SORT_BY_COLOR, false); + //this.list.setSelector(R.drawable.highlight_disabled_pressed); + list.setOnItemClickListener(new OnItemClickListener() { + + public synchronized void onItemClick(AdapterView<?> parent, View view, int position, long id) { + // launch off to console details + HostBean host = (HostBean) parent.getAdapter().getItem(position); + Uri uri = host.getUri(); + 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); + 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(); + } + else { + // otherwise just launch activity to show this host + HostListActivity.this.startActivity(contents); + } + } + }); + this.registerForContextMenu(list); + quickconnect = (TextView) this.findViewById(R.id.front_quickconnect); + quickconnect.setVisibility(makingShortcut ? View.GONE : View.VISIBLE); + quickconnect.setOnKeyListener(new OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_UP) return false; + + if (keyCode != KeyEvent.KEYCODE_ENTER) return false; + + return startConsoleActivity(); + } + }); + transportSpinner = (Spinner)findViewById(R.id.transport_selection); + transportSpinner.setVisibility(makingShortcut ? View.GONE : View.VISIBLE); + ArrayAdapter<String> transportSelection = new ArrayAdapter<String> (this, + android.R.layout.simple_spinner_item, TransportFactory.getTransportNames()); + transportSelection.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + transportSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + public void onItemSelected(AdapterView<?> arg0, View view, int position, long id) { + String formatHint = TransportFactory.getFormatHint( + (String) transportSpinner.getSelectedItem(), + HostListActivity.this); + quickconnect.setHint(formatHint); + quickconnect.setError(null); + quickconnect.requestFocus(); + } + public void onNothingSelected(AdapterView<?> arg0) { } + }); + transportSpinner.setAdapter(transportSelection); + this.inflater = LayoutInflater.from(this); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + // don't offer menus when creating shortcut + if (makingShortcut) return true; + + sortcolor.setVisible(!sortedByColor); + sortlast.setVisible(sortedByColor); + return true; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + // don't offer menus when creating shortcut + if (makingShortcut) return true; + + // add host, ssh keys, about + sortcolor = menu.add(R.string.list_menu_sortcolor); + sortcolor.setIcon(android.R.drawable.ic_menu_share); + sortcolor.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + sortedByColor = true; + updateList(); + return true; + } + }); + sortlast = menu.add(R.string.list_menu_sortname); + sortlast.setIcon(android.R.drawable.ic_menu_share); + sortlast.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + sortedByColor = false; + updateList(); + return true; + } + }); + MenuItem keys = menu.add(R.string.list_menu_pubkeys); + keys.setIcon(android.R.drawable.ic_lock_lock); + keys.setIntent(new Intent(HostListActivity.this, PubkeyListActivity.class)); + MenuItem colors = menu.add(R.string.title_colors); + colors.setIcon(android.R.drawable.ic_menu_slideshow); + colors.setIntent(new Intent(HostListActivity.this, ColorsActivity.class)); + MenuItem settings = menu.add(R.string.list_menu_settings); + settings.setIcon(android.R.drawable.ic_menu_preferences); + settings.setIntent(new Intent(HostListActivity.this, SettingsActivity.class)); + MenuItem help = menu.add(R.string.title_help); + help.setIcon(android.R.drawable.ic_menu_help); + help.setIntent(new Intent(HostListActivity.this, HelpActivity.class)); + return true; + } + + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + // create menu to handle hosts + // create menu to handle deleting and sharing lists + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + final HostBean host = (HostBean) this.getListView().getItemAtPosition(info.position); + menu.setHeaderTitle(host.getNickname()); + // edit, disconnect, delete + MenuItem connect = menu.add(R.string.list_host_disconnect); + final TerminalBridge bridge = bound.getConnectedBridge(host); + connect.setEnabled((bridge != null)); + connect.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + bridge.dispatchDisconnect(true); + updateHandler.sendEmptyMessage(-1); + 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); + updateHandler.sendEmptyMessage(-1); + } + }) + .setNegativeButton(R.string.delete_neg, null).create().show(); + return true; + } + }); + } + + /** + * @param text + * @return + */ + private boolean startConsoleActivity() { + Uri uri = TransportFactory.getUri((String) transportSpinner + .getSelectedItem(), quickconnect.getText().toString()); + + if (uri == null) { + quickconnect.setError(getString(R.string.list_format_error, + TransportFactory.getFormatHint( + (String) transportSpinner.getSelectedItem(), + HostListActivity.this))); + return false; + } + + HostBean host = TransportFactory.findHost(hostdb, uri); + + if (host == null) { + host = TransportFactory.getTransport(uri.getScheme()).createHost(uri); + host.setColor(HostDatabase.COLOR_GRAY); + host.setPubkeyId(HostDatabase.PUBKEYID_ANY); + hostdb.saveHost(host); + } + + Intent intent = new Intent(HostListActivity.this, ConsoleActivity.class); + intent.setData(uri); + startActivity(intent); + return true; + } + + protected void updateList() { + if (prefs.getBoolean(PreferenceConstants.SORT_BY_COLOR, false) != sortedByColor) { + Editor edit = prefs.edit(); + edit.putBoolean(PreferenceConstants.SORT_BY_COLOR, sortedByColor); + edit.commit(); + } + + if (hostdb == null) + hostdb = new HostDatabase(this); + + hosts = hostdb.getHosts(sortedByColor); + + // Don't lose hosts that are connected via shortcuts but not in the database. + if (bound != null) { + for (TerminalBridge bridge : bound.bridges) { + if (!hosts.contains(bridge.host)) + hosts.add(0, bridge.host); + } + } + + HostAdapter adapter = new HostAdapter(this, hosts, bound); + this.setListAdapter(adapter); + } + + class HostAdapter extends ArrayAdapter<HostBean> { + private List<HostBean> hosts; + private final TerminalManager manager; + private final ColorStateList red, green, blue; + + public final static int STATE_UNKNOWN = 1, STATE_CONNECTED = 2, STATE_DISCONNECTED = 3; + + class ViewHolder { + public TextView nickname; + public TextView caption; + public ImageView icon; + } + + public HostAdapter(Context context, List<HostBean> hosts, TerminalManager manager) { + super(context, R.layout.item_host, hosts); + this.hosts = hosts; + this.manager = manager; + red = context.getResources().getColorStateList(R.color.red); + green = context.getResources().getColorStateList(R.color.green); + blue = context.getResources().getColorStateList(R.color.blue); + } + + /** + * Check if we're connected to a terminal with the given host. + */ + private int getConnectedState(HostBean host) { + // always disconnected if we dont have backend service + if (this.manager == null) + return STATE_UNKNOWN; + + if (manager.getConnectedBridge(host) != null) + return STATE_CONNECTED; + + if (manager.disconnected.contains(host)) + return STATE_DISCONNECTED; + + return STATE_UNKNOWN; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + + if (convertView == null) { + convertView = inflater.inflate(R.layout.item_host, null, false); + holder = new ViewHolder(); + 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.icon); + convertView.setTag(holder); + } + else + holder = (ViewHolder) convertView.getTag(); + + 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"); + return convertView; + } + + holder.nickname.setText(host.getNickname()); + + switch (this.getConnectedState(host)) { + case STATE_UNKNOWN: + holder.icon.setImageState(new int[] { }, true); + break; + + case STATE_CONNECTED: + holder.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); + break; + } + + ColorStateList chosen = null; + + if (HostDatabase.COLOR_RED.equals(host.getColor())) + chosen = this.red; + else if (HostDatabase.COLOR_GREEN.equals(host.getColor())) + chosen = this.green; + else if (HostDatabase.COLOR_BLUE.equals(host.getColor())) + chosen = this.blue; + + Context context = convertView.getContext(); + + if (chosen != null) { + // set color normally if not selected + holder.nickname.setTextColor(chosen); + holder.caption.setTextColor(chosen); + } + else { + // selected, so revert back to default black text + holder.nickname.setTextAppearance(context, android.R.attr.textAppearanceLarge); + holder.caption.setTextAppearance(context, android.R.attr.textAppearanceSmall); + } + + long now = System.currentTimeMillis() / 1000; + String nice = context.getString(R.string.bind_never); + + if (host.getLastConnect() > 0) { + int minutes = (int)((now - host.getLastConnect()) / 60); + + if (minutes >= 60) { + int hours = (minutes / 60); + + if (hours >= 24) { + int days = (hours / 24); + nice = context.getString(R.string.bind_days, days); + } + else + nice = context.getString(R.string.bind_hours, hours); + } + else + nice = context.getString(R.string.bind_minutes, minutes); + } + + holder.caption.setText(nice); + return convertView; + } + } +}