1/* 2 * ConnectBot: simple, powerful, open-source SSH client for Android 3 * Copyright 2007 Kenny Root, Jeffrey Sharkey 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package org.connectbot.service; 19 20import android.content.Context; 21import android.content.Intent; 22import android.content.SharedPreferences; 23import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 24import android.content.res.AssetFileDescriptor; 25import android.content.res.Configuration; 26import android.content.res.Resources; 27import android.media.AudioManager; 28import android.media.MediaPlayer; 29import android.media.MediaPlayer.OnCompletionListener; 30import android.os.Handler; 31import android.os.Message; 32import android.os.Vibrator; 33import android.preference.PreferenceManager; 34 35import com.googlecode.android_scripting.Constants; 36import com.googlecode.android_scripting.Log; 37import com.googlecode.android_scripting.R; 38import com.googlecode.android_scripting.activity.ScriptingLayerService; 39import com.googlecode.android_scripting.exception.Sl4aException; 40import com.googlecode.android_scripting.interpreter.InterpreterProcess; 41 42import org.connectbot.transport.ProcessTransport; 43import org.connectbot.util.PreferenceConstants; 44 45import java.io.IOException; 46import java.lang.ref.WeakReference; 47import java.util.List; 48import java.util.Map; 49import java.util.concurrent.ConcurrentHashMap; 50import java.util.concurrent.CopyOnWriteArrayList; 51 52/** 53 * Manager for SSH connections that runs as a background service. This service holds a list of 54 * currently connected SSH bridges that are ready for connection up to a GUI if needed. 55 * 56 */ 57public class TerminalManager implements OnSharedPreferenceChangeListener { 58 59 private static final long VIBRATE_DURATION = 30; 60 61 private final List<TerminalBridge> bridges = new CopyOnWriteArrayList<TerminalBridge>(); 62 63 private final Map<Integer, WeakReference<TerminalBridge>> mHostBridgeMap = 64 new ConcurrentHashMap<Integer, WeakReference<TerminalBridge>>(); 65 66 private Handler mDisconnectHandler = null; 67 68 private final Resources mResources; 69 70 private final SharedPreferences mPreferences; 71 72 private boolean hardKeyboardHidden; 73 74 private Vibrator vibrator; 75 private boolean wantKeyVibration; 76 private boolean wantBellVibration; 77 private boolean wantAudible; 78 private boolean resizeAllowed = false; 79 private MediaPlayer mediaPlayer; 80 81 private final ScriptingLayerService mService; 82 83 public TerminalManager(ScriptingLayerService service) { 84 mService = service; 85 mPreferences = PreferenceManager.getDefaultSharedPreferences(mService); 86 registerOnSharedPreferenceChangeListener(this); 87 mResources = mService.getResources(); 88 hardKeyboardHidden = 89 (mResources.getConfiguration().hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES); 90 vibrator = (Vibrator) mService.getSystemService(Context.VIBRATOR_SERVICE); 91 wantKeyVibration = mPreferences.getBoolean(PreferenceConstants.BUMPY_ARROWS, true); 92 wantBellVibration = mPreferences.getBoolean(PreferenceConstants.BELL_VIBRATE, true); 93 wantAudible = mPreferences.getBoolean(PreferenceConstants.BELL, true); 94 if (wantAudible) { 95 enableMediaPlayer(); 96 } 97 } 98 99 /** 100 * Disconnect all currently connected bridges. 101 */ 102 private void disconnectAll() { 103 TerminalBridge[] bridgesArray = null; 104 if (bridges.size() > 0) { 105 bridgesArray = bridges.toArray(new TerminalBridge[bridges.size()]); 106 } 107 if (bridgesArray != null) { 108 // disconnect and dispose of any existing bridges 109 for (TerminalBridge bridge : bridgesArray) { 110 bridge.dispatchDisconnect(true); 111 } 112 } 113 } 114 115 /** 116 * Open a new session using the given parameters. 117 * 118 * @throws InterruptedException 119 * @throws Sl4aException 120 */ 121 public TerminalBridge openConnection(int id) throws IllegalArgumentException, IOException, 122 InterruptedException, Sl4aException { 123 // throw exception if terminal already open 124 if (getConnectedBridge(id) != null) { 125 throw new IllegalArgumentException("Connection already open"); 126 } 127 128 InterpreterProcess process = mService.getProcess(id); 129 130 TerminalBridge bridge = new TerminalBridge(this, process, new ProcessTransport(process)); 131 bridge.connect(); 132 133 WeakReference<TerminalBridge> wr = new WeakReference<TerminalBridge>(bridge); 134 bridges.add(bridge); 135 mHostBridgeMap.put(id, wr); 136 137 return bridge; 138 } 139 140 /** 141 * Find a connected {@link TerminalBridge} with the given HostBean. 142 * 143 * @param id 144 * the HostBean to search for 145 * @return TerminalBridge that uses the HostBean 146 */ 147 public TerminalBridge getConnectedBridge(int id) { 148 WeakReference<TerminalBridge> wr = mHostBridgeMap.get(id); 149 if (wr != null) { 150 return wr.get(); 151 } else { 152 return null; 153 } 154 } 155 156 /** 157 * Called by child bridge when somehow it's been disconnected. 158 */ 159 public void closeConnection(TerminalBridge bridge, boolean killProcess) { 160 if (killProcess) { 161 bridges.remove(bridge); 162 mHostBridgeMap.remove(bridge.getId()); 163 if (mService.getProcess(bridge.getId()).isAlive()) { 164 Intent intent = new Intent(mService, mService.getClass()); 165 intent.setAction(Constants.ACTION_KILL_PROCESS); 166 intent.putExtra(Constants.EXTRA_PROXY_PORT, bridge.getId()); 167 mService.startService(intent); 168 } 169 } 170 if (mDisconnectHandler != null) { 171 Message.obtain(mDisconnectHandler, -1, bridge).sendToTarget(); 172 } 173 } 174 175 /** 176 * Allow {@link TerminalBridge} to resize when the parent has changed. 177 * 178 * @param resizeAllowed 179 */ 180 public void setResizeAllowed(boolean resizeAllowed) { 181 this.resizeAllowed = resizeAllowed; 182 } 183 184 public boolean isResizeAllowed() { 185 return resizeAllowed; 186 } 187 188 public void stop() { 189 resizeAllowed = false; 190 disconnectAll(); 191 disableMediaPlayer(); 192 } 193 194 public int getIntParameter(String key, int defValue) { 195 return mPreferences.getInt(key, defValue); 196 } 197 198 public String getStringParameter(String key, String defValue) { 199 return mPreferences.getString(key, defValue); 200 } 201 202 public void tryKeyVibrate() { 203 if (wantKeyVibration) { 204 vibrate(); 205 } 206 } 207 208 private void vibrate() { 209 if (vibrator != null) { 210 vibrator.vibrate(VIBRATE_DURATION); 211 } 212 } 213 214 private void enableMediaPlayer() { 215 mediaPlayer = new MediaPlayer(); 216 217 float volume = 218 mPreferences.getFloat(PreferenceConstants.BELL_VOLUME, 219 PreferenceConstants.DEFAULT_BELL_VOLUME); 220 221 mediaPlayer.setAudioStreamType(AudioManager.STREAM_NOTIFICATION); 222 mediaPlayer.setOnCompletionListener(new BeepListener()); 223 224 AssetFileDescriptor file = mResources.openRawResourceFd(R.raw.bell); 225 try { 226 mediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength()); 227 file.close(); 228 mediaPlayer.setVolume(volume, volume); 229 mediaPlayer.prepare(); 230 } catch (IOException e) { 231 Log.e("Error setting up bell media player", e); 232 } 233 } 234 235 private void disableMediaPlayer() { 236 if (mediaPlayer != null) { 237 mediaPlayer.release(); 238 mediaPlayer = null; 239 } 240 } 241 242 public void playBeep() { 243 if (mediaPlayer != null) { 244 mediaPlayer.start(); 245 } 246 if (wantBellVibration) { 247 vibrate(); 248 } 249 } 250 251 private static class BeepListener implements OnCompletionListener { 252 public void onCompletion(MediaPlayer mp) { 253 mp.seekTo(0); 254 } 255 } 256 257 public boolean isHardKeyboardHidden() { 258 return hardKeyboardHidden; 259 } 260 261 public void setHardKeyboardHidden(boolean b) { 262 hardKeyboardHidden = b; 263 } 264 265 @Override 266 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 267 if (PreferenceConstants.BELL.equals(key)) { 268 wantAudible = sharedPreferences.getBoolean(PreferenceConstants.BELL, true); 269 if (wantAudible && mediaPlayer == null) { 270 enableMediaPlayer(); 271 } else if (!wantAudible && mediaPlayer != null) { 272 disableMediaPlayer(); 273 } 274 } else if (PreferenceConstants.BELL_VOLUME.equals(key)) { 275 if (mediaPlayer != null) { 276 float volume = 277 sharedPreferences.getFloat(PreferenceConstants.BELL_VOLUME, 278 PreferenceConstants.DEFAULT_BELL_VOLUME); 279 mediaPlayer.setVolume(volume, volume); 280 } 281 } else if (PreferenceConstants.BELL_VIBRATE.equals(key)) { 282 wantBellVibration = sharedPreferences.getBoolean(PreferenceConstants.BELL_VIBRATE, true); 283 } else if (PreferenceConstants.BUMPY_ARROWS.equals(key)) { 284 wantKeyVibration = sharedPreferences.getBoolean(PreferenceConstants.BUMPY_ARROWS, true); 285 } 286 } 287 288 public void setDisconnectHandler(Handler disconnectHandler) { 289 mDisconnectHandler = disconnectHandler; 290 } 291 292 public List<TerminalBridge> getBridgeList() { 293 return bridges; 294 } 295 296 public Resources getResources() { 297 return mResources; 298 } 299 300 public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { 301 mPreferences.registerOnSharedPreferenceChangeListener(listener); 302 } 303 304} 305