/* * 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.service; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.res.AssetFileDescriptor; import android.content.res.Configuration; import android.content.res.Resources; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.os.Handler; import android.os.Message; import android.os.Vibrator; import android.preference.PreferenceManager; import com.googlecode.android_scripting.Constants; import com.googlecode.android_scripting.Log; import com.googlecode.android_scripting.R; import com.googlecode.android_scripting.activity.ScriptingLayerService; import com.googlecode.android_scripting.exception.Sl4aException; import com.googlecode.android_scripting.interpreter.InterpreterProcess; import org.connectbot.transport.ProcessTransport; import org.connectbot.util.PreferenceConstants; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; /** * Manager for SSH connections that runs as a background service. This service holds a list of * currently connected SSH bridges that are ready for connection up to a GUI if needed. * */ public class TerminalManager implements OnSharedPreferenceChangeListener { private static final long VIBRATE_DURATION = 30; private final List<TerminalBridge> bridges = new CopyOnWriteArrayList<TerminalBridge>(); private final Map<Integer, WeakReference<TerminalBridge>> mHostBridgeMap = new ConcurrentHashMap<Integer, WeakReference<TerminalBridge>>(); private Handler mDisconnectHandler = null; private final Resources mResources; private final SharedPreferences mPreferences; private boolean hardKeyboardHidden; private Vibrator vibrator; private boolean wantKeyVibration; private boolean wantBellVibration; private boolean wantAudible; private boolean resizeAllowed = false; private MediaPlayer mediaPlayer; private final ScriptingLayerService mService; public TerminalManager(ScriptingLayerService service) { mService = service; mPreferences = PreferenceManager.getDefaultSharedPreferences(mService); registerOnSharedPreferenceChangeListener(this); mResources = mService.getResources(); hardKeyboardHidden = (mResources.getConfiguration().hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES); vibrator = (Vibrator) mService.getSystemService(Context.VIBRATOR_SERVICE); wantKeyVibration = mPreferences.getBoolean(PreferenceConstants.BUMPY_ARROWS, true); wantBellVibration = mPreferences.getBoolean(PreferenceConstants.BELL_VIBRATE, true); wantAudible = mPreferences.getBoolean(PreferenceConstants.BELL, true); if (wantAudible) { enableMediaPlayer(); } } /** * Disconnect all currently connected bridges. */ private void disconnectAll() { TerminalBridge[] bridgesArray = null; if (bridges.size() > 0) { bridgesArray = bridges.toArray(new TerminalBridge[bridges.size()]); } if (bridgesArray != null) { // disconnect and dispose of any existing bridges for (TerminalBridge bridge : bridgesArray) { bridge.dispatchDisconnect(true); } } } /** * Open a new session using the given parameters. * * @throws InterruptedException * @throws Sl4aException */ public TerminalBridge openConnection(int id) throws IllegalArgumentException, IOException, InterruptedException, Sl4aException { // throw exception if terminal already open if (getConnectedBridge(id) != null) { throw new IllegalArgumentException("Connection already open"); } InterpreterProcess process = mService.getProcess(id); TerminalBridge bridge = new TerminalBridge(this, process, new ProcessTransport(process)); bridge.connect(); WeakReference<TerminalBridge> wr = new WeakReference<TerminalBridge>(bridge); bridges.add(bridge); mHostBridgeMap.put(id, wr); return bridge; } /** * Find a connected {@link TerminalBridge} with the given HostBean. * * @param id * the HostBean to search for * @return TerminalBridge that uses the HostBean */ public TerminalBridge getConnectedBridge(int id) { WeakReference<TerminalBridge> wr = mHostBridgeMap.get(id); if (wr != null) { return wr.get(); } else { return null; } } /** * Called by child bridge when somehow it's been disconnected. */ public void closeConnection(TerminalBridge bridge, boolean killProcess) { if (killProcess) { bridges.remove(bridge); mHostBridgeMap.remove(bridge.getId()); if (mService.getProcess(bridge.getId()).isAlive()) { Intent intent = new Intent(mService, mService.getClass()); intent.setAction(Constants.ACTION_KILL_PROCESS); intent.putExtra(Constants.EXTRA_PROXY_PORT, bridge.getId()); mService.startService(intent); } } if (mDisconnectHandler != null) { Message.obtain(mDisconnectHandler, -1, bridge).sendToTarget(); } } /** * Allow {@link TerminalBridge} to resize when the parent has changed. * * @param resizeAllowed */ public void setResizeAllowed(boolean resizeAllowed) { this.resizeAllowed = resizeAllowed; } public boolean isResizeAllowed() { return resizeAllowed; } public void stop() { resizeAllowed = false; disconnectAll(); disableMediaPlayer(); } public int getIntParameter(String key, int defValue) { return mPreferences.getInt(key, defValue); } public String getStringParameter(String key, String defValue) { return mPreferences.getString(key, defValue); } public void tryKeyVibrate() { if (wantKeyVibration) { vibrate(); } } private void vibrate() { if (vibrator != null) { vibrator.vibrate(VIBRATE_DURATION); } } private void enableMediaPlayer() { mediaPlayer = new MediaPlayer(); float volume = mPreferences.getFloat(PreferenceConstants.BELL_VOLUME, PreferenceConstants.DEFAULT_BELL_VOLUME); mediaPlayer.setAudioStreamType(AudioManager.STREAM_NOTIFICATION); mediaPlayer.setOnCompletionListener(new BeepListener()); AssetFileDescriptor file = mResources.openRawResourceFd(R.raw.bell); try { mediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength()); file.close(); mediaPlayer.setVolume(volume, volume); mediaPlayer.prepare(); } catch (IOException e) { Log.e("Error setting up bell media player", e); } } private void disableMediaPlayer() { if (mediaPlayer != null) { mediaPlayer.release(); mediaPlayer = null; } } public void playBeep() { if (mediaPlayer != null) { mediaPlayer.start(); } if (wantBellVibration) { vibrate(); } } private static class BeepListener implements OnCompletionListener { public void onCompletion(MediaPlayer mp) { mp.seekTo(0); } } public boolean isHardKeyboardHidden() { return hardKeyboardHidden; } public void setHardKeyboardHidden(boolean b) { hardKeyboardHidden = b; } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (PreferenceConstants.BELL.equals(key)) { wantAudible = sharedPreferences.getBoolean(PreferenceConstants.BELL, true); if (wantAudible && mediaPlayer == null) { enableMediaPlayer(); } else if (!wantAudible && mediaPlayer != null) { disableMediaPlayer(); } } else if (PreferenceConstants.BELL_VOLUME.equals(key)) { if (mediaPlayer != null) { float volume = sharedPreferences.getFloat(PreferenceConstants.BELL_VOLUME, PreferenceConstants.DEFAULT_BELL_VOLUME); mediaPlayer.setVolume(volume, volume); } } else if (PreferenceConstants.BELL_VIBRATE.equals(key)) { wantBellVibration = sharedPreferences.getBoolean(PreferenceConstants.BELL_VIBRATE, true); } else if (PreferenceConstants.BUMPY_ARROWS.equals(key)) { wantKeyVibration = sharedPreferences.getBoolean(PreferenceConstants.BUMPY_ARROWS, true); } } public void setDisconnectHandler(Handler disconnectHandler) { mDisconnectHandler = disconnectHandler; } public List<TerminalBridge> getBridgeList() { return bridges; } public Resources getResources() { return mResources; } public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { mPreferences.registerOnSharedPreferenceChangeListener(listener); } }