view src/com/five_ten_sg/connectbot/HostEditorActivity.java @ 398:2a416391ffc3

add queue to buffer monitor socket writes to prevent blocking on socket output stream write
author Carl Byington <carl@five-ten-sg.com>
date Wed, 15 Oct 2014 18:00:06 -0700
parents 071eccdff8ea
children
line wrap: on
line source

/*
 * 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.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

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.util.HostDatabase;
import com.five_ten_sg.connectbot.util.PubkeyDatabase;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
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 CursorPreferenceHack implements SharedPreferences {
        protected final String table;
        protected final long id;

        protected Map<String, String> values = new HashMap<String, String>();

        public CursorPreferenceHack(String table, long id) {
            this.table = table;
            this.id = id;
            cacheValues();
        }

        protected final void cacheValues() {
            // fill a cursor and cache the values locally
            // this makes sure we dont have any floating cursor to dispose later
            SQLiteDatabase db = hostdb.getReadableDatabase();
            Cursor cursor = db.query(table, null, "_id = ?",
                                     new String[] { String.valueOf(id) }, null, null, null);

            if (cursor.moveToFirst()) {
                for (int i = 0; i < cursor.getColumnCount(); i++) {
                    String key = cursor.getColumnName(i);

                    if (key.equals(HostDatabase.FIELD_HOST_HOSTKEY)) continue;

                    String value = cursor.getString(i);
                    values.put(key, value);
                }
            }

            cursor.close();
            db.close();
        }

        public boolean contains(String key) {
            return values.containsKey(key);
        }

        public class Editor implements SharedPreferences.Editor {

            private ContentValues update = new ContentValues();

            public SharedPreferences.Editor clear() {
                Log.d(this.getClass().toString(), "clear()");
                update = new ContentValues();
                return this;
            }

            public boolean commit() {
                //Log.d(this.getClass().toString(), "commit() changes back to database");
                SQLiteDatabase db = hostdb.getWritableDatabase();
                db.update(table, update, "_id = ?", new String[] { String.valueOf(id) });
                db.close();
                // make sure we refresh the parent cached values
                cacheValues();

                // and update any listeners
                for (OnSharedPreferenceChangeListener listener : listeners) {
                    listener.onSharedPreferenceChanged(CursorPreferenceHack.this, null);
                }

                return true;
            }

            // Gingerbread compatibility
            public void apply() {
                commit();
            }

            public android.content.SharedPreferences.Editor putBoolean(String key, boolean value) {
                return this.putString(key, Boolean.toString(value));
            }

            public android.content.SharedPreferences.Editor putFloat(String key, float value) {
                return this.putString(key, Float.toString(value));
            }

            public android.content.SharedPreferences.Editor putInt(String key, int value) {
                return this.putString(key, Integer.toString(value));
            }

            public android.content.SharedPreferences.Editor putLong(String key, long value) {
                return this.putString(key, Long.toString(value));
            }

            public android.content.SharedPreferences.Editor putString(String key, String value) {
                //Log.d(this.getClass().toString(), String.format("Editor.putString(key=%s, value=%s)", key, value));
                update.put(key, value);
                return this;
            }

            public android.content.SharedPreferences.Editor remove(String key) {
                //Log.d(this.getClass().toString(), String.format("Editor.remove(key=%s)", key));
                update.remove(key);
                return this;
            }

            public android.content.SharedPreferences.Editor putStringSet(String key, Set<String> value) {
                throw new UnsupportedOperationException("HostEditor Prefs do not support Set<String>");
            }
        }


        public Editor edit() {
            //Log.d(this.getClass().toString(), "edit()");
            return new Editor();
        }

        public Map<String, ?> getAll() {
            return values;
        }

        public boolean getBoolean(String key, boolean defValue) {
            return Boolean.valueOf(this.getString(key, Boolean.toString(defValue)));
        }

        public float getFloat(String key, float defValue) {
            return Float.valueOf(this.getString(key, Float.toString(defValue)));
        }

        public int getInt(String key, int defValue) {
            return Integer.valueOf(this.getString(key, Integer.toString(defValue)));
        }

        public long getLong(String key, long defValue) {
            return Long.valueOf(this.getString(key, Long.toString(defValue)));
        }

        public String getString(String key, String defValue) {
            //Log.d(this.getClass().toString(), String.format("getString(key=%s, defValue=%s)", key, defValue));
            if (!values.containsKey(key)) return defValue;

            return values.get(key);
        }

        public Set<String> getStringSet(String key, Set<String> defValue) {
            throw new ClassCastException("HostEditor Prefs do not support Set<String>");
        }

        protected List<OnSharedPreferenceChangeListener> listeners = new LinkedList<OnSharedPreferenceChangeListener>();

        public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
            listeners.add(listener);
        }

        public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
            listeners.remove(listener);
        }

    }

    @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        //Log.d(this.getClass().toString(), String.format("getSharedPreferences(name=%s)", name));
        return this.pref;
    }

    protected static final String TAG = "ConnectBot.HostEditorActivity";

    protected HostDatabase hostdb = null;
    private PubkeyDatabase pubkeydb = null;

    private CursorPreferenceHack pref;
    private ServiceConnection connection;
    private Map<String, CharSequence> summaries = new HashMap<String, CharSequence>();

    private HostBean host;
    private boolean enableSSHFeatures;
    private boolean enable5250Features;
    private boolean enableAsyncFeatures;

    protected TerminalBridge hostBridge;

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        long hostId = this.getIntent().getLongExtra(Intent.EXTRA_TITLE, -1);
        // TODO: we could pass through a specific ContentProvider uri here
        //this.getPreferenceManager().setSharedPreferencesName(uri);
        this.hostdb = new HostDatabase(this);
        this.pubkeydb = new PubkeyDatabase(this);
        host = hostdb.findHostById(hostId);
        enableSSHFeatures   = host.isSSH();
        enable5250Features  = host.is5250();
        enableAsyncFeatures = host.isAsync();
        connection = new ServiceConnection() {
            public void onServiceConnected(ComponentName className, IBinder service) {
                TerminalManager bound = ((TerminalManager.TerminalBinder) service).getService();
                hostBridge = bound.getConnectedBridge(host);
            }
            public void onServiceDisconnected(ComponentName name) {
                hostBridge = null;
            }
        };
        this.pref = new CursorPreferenceHack(HostDatabase.TABLE_HOSTS, hostId);
        this.pref.registerOnSharedPreferenceChangeListener(this);
        this.addPreferencesFromResource(R.xml.host_prefs);
        // get all the text summaries
        getSummaries();
        // disable all preferences that are not applicable to this host
        findPreference(HostDatabase.FIELD_HOST_PUBKEYID).setEnabled(enableSSHFeatures);
        findPreference(HostDatabase.FIELD_HOST_USEAUTHAGENT).setEnabled(enableSSHFeatures);
        findPreference(HostDatabase.FIELD_HOST_POSTLOGIN).setEnabled(!enable5250Features);
        findPreference(HostDatabase.FIELD_HOST_COMPRESSION).setEnabled(enableSSHFeatures);
        findPreference(HostDatabase.FIELD_HOST_HTTPPROXY).setEnabled(enableAsyncFeatures);
        findPreference(HostDatabase.FIELD_HOST_WANTSESSION).setEnabled(enableSSHFeatures);
        findPreference(HostDatabase.FIELD_HOST_USERNAME).setEnabled(enableSSHFeatures || enable5250Features);
        findPreference(HostDatabase.FIELD_HOST_EMULATION).setEnabled(!enable5250Features);
        findPreference(HostDatabase.CATEGORY_5250).setEnabled(enable5250Features);
        findPreference(HostDatabase.CATEGORY_X11).setEnabled(enableSSHFeatures);
        // add all existing pubkeys to our listpreference for user to choose from
        // TODO: may be an issue here when this activity is recycled after adding a new pubkey
        // TODO: should consider moving into onStart, but we dont have a good way of resetting the listpref after filling once
        ListPreference pubkeyPref = (ListPreference)findPreference(HostDatabase.FIELD_HOST_PUBKEYID);
        List<CharSequence> pubkeyNicks = new LinkedList<CharSequence> (Arrays.asList(pubkeyPref.getEntries()));
        pubkeyNicks.addAll(pubkeydb.allValues(PubkeyDatabase.FIELD_PUBKEY_NICKNAME));
        pubkeyPref.setEntries(pubkeyNicks.toArray(new CharSequence[pubkeyNicks.size()]));
        List<CharSequence> pubkeyIds = new LinkedList<CharSequence> (Arrays.asList(pubkeyPref.getEntryValues()));
        pubkeyIds.addAll(pubkeydb.allValues("_id"));
        pubkeyPref.setEntryValues(pubkeyIds.toArray(new CharSequence[pubkeyIds.size()]));
        // Populate the character set encoding list with all available
        final ListPreference charsetPref = (ListPreference)findPreference(HostDatabase.FIELD_HOST_ENCODING);

        if (CharsetHolder.isInitialized()) {
            initCharsetPref(charsetPref);
        }
        else {
            String[] currentCharsetPref = new String[1];
            currentCharsetPref[0] = charsetPref.getValue();
            charsetPref.setEntryValues(currentCharsetPref);
            charsetPref.setEntries(currentCharsetPref);
            new Thread(new Runnable() {
                public void run() {
                    initCharsetPref(charsetPref);
                }
            }).start();
        }

        this.updateSummaries();
    }

    @Override
    public void onStart() {
        super.onStart();
        bindService(new Intent(this, TerminalManager.class), connection, Context.BIND_AUTO_CREATE);

        if (this.hostdb == null)
            this.hostdb = new HostDatabase(this);

        if (this.pubkeydb == null)
            this.pubkeydb = new PubkeyDatabase(this);
    }

    @Override
    public void onStop() {
        super.onStop();
        unbindService(connection);

        if (this.hostdb != null) {
            this.hostdb.close();
            this.hostdb = null;
        }

        if (this.pubkeydb != null) {
            this.pubkeydb.close();
            this.pubkeydb = null;
        }
    }

    private void getSummaries() {
        // get all the original text summaries
        for (String key : this.pref.values.keySet()) {
            Preference pref = this.findPreference(key);

            if (pref == null) continue;

            if (pref instanceof CheckBoxPreference) continue;

            CharSequence value = pref.getSummary();
            summaries.put(key, value);
        }
    }

    private void updateSummaries() {
        // for all text preferences, set hint as current database value if it is non-empty
        for (String key : this.pref.values.keySet()) {
            Preference pref = this.findPreference(key);

            if (pref == null) continue;

            if (pref instanceof CheckBoxPreference) continue;

            CharSequence value = this.pref.getString(key, "");

            if (key.equals(HostDatabase.FIELD_HOST_PUBKEYID)) {
                try {
                    int pubkeyId = Integer.parseInt(value.toString());

                    if (pubkeyId >= 0)
                        pref.setSummary(pubkeydb.getNickname(pubkeyId));
                    else if (pubkeyId == HostDatabase.PUBKEYID_ANY)
                        pref.setSummary(R.string.list_pubkeyids_any);
                    else if (pubkeyId == HostDatabase.PUBKEYID_NEVER)
                        pref.setSummary(R.string.list_pubkeyids_none);

                    continue;
                }
                catch (NumberFormatException nfe) {
                    // Fall through.
                }
            }
            else if ((pref instanceof ListPreference) && (value != null)) {
                ListPreference listPref = (ListPreference) pref;
                int entryIndex = listPref.findIndexOfValue(value.toString());

                if (entryIndex >= 0) value = listPref.getEntries()[entryIndex];
            }

            if ((value == null) || (value.length() == 0)) value = summaries.get(key);

            pref.setSummary(value);
        }
    }

    private void initCharsetPref(final ListPreference charsetPref) {
        charsetPref.setEntryValues(CharsetHolder.getCharsetIds());
        charsetPref.setEntries(CharsetHolder.getCharsetNames());
    }

    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        // update values on changed preference
        this.updateSummaries();

        // Our CursorPreferenceHack always send null keys, so try to set charset anyway
        if (hostBridge != null)
            hostBridge.setCharset(sharedPreferences
                                  .getString(HostDatabase.FIELD_HOST_ENCODING, HostDatabase.ENCODING_DEFAULT));
    }

    public static class CharsetHolder {
        private static boolean initialized = false;

        private static CharSequence[] charsetIds;
        private static CharSequence[] charsetNames;

        public static CharSequence[] getCharsetNames() {
            if (charsetNames == null)
                initialize();

            return charsetNames;
        }

        public static CharSequence[] getCharsetIds() {
            if (charsetIds == null)
                initialize();

            return charsetIds;
        }

        private synchronized static void initialize() {
            if (initialized)
                return;

            List<CharSequence> charsetIdsList = new LinkedList<CharSequence>();
            List<CharSequence> charsetNamesList = new LinkedList<CharSequence>();

            for (Entry<String, Charset> entry : Charset.availableCharsets().entrySet()) {
                Charset c = entry.getValue();

                if (c.canEncode() && c.isRegistered()) {
                    String key = entry.getKey();

                    if (key.startsWith("cp")) {
                        // Custom CP437 charset changes
                        charsetIdsList.add("CP437");
                        charsetNamesList.add("CP437");
                    }

                    charsetIdsList.add(entry.getKey());
                    charsetNamesList.add(c.displayName());
                }
            }

            charsetIds = charsetIdsList.toArray(new CharSequence[charsetIdsList.size()]);
            charsetNames = charsetNamesList.toArray(new CharSequence[charsetNamesList.size()]);
            initialized = true;
        }

        public static boolean isInitialized() {
            return initialized;
        }
    }
}