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 18 package org.connectbot.service; 19 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.SharedPreferences; 23 import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 24 import android.content.res.AssetFileDescriptor; 25 import android.content.res.Configuration; 26 import android.content.res.Resources; 27 import android.media.AudioManager; 28 import android.media.MediaPlayer; 29 import android.media.MediaPlayer.OnCompletionListener; 30 import android.os.Handler; 31 import android.os.Message; 32 import android.os.Vibrator; 33 import android.preference.PreferenceManager; 34 35 import com.googlecode.android_scripting.Constants; 36 import com.googlecode.android_scripting.Log; 37 import com.googlecode.android_scripting.R; 38 import com.googlecode.android_scripting.service.ScriptingLayerService; 39 import com.googlecode.android_scripting.exception.Sl4aException; 40 import com.googlecode.android_scripting.interpreter.InterpreterProcess; 41 42 import org.connectbot.transport.ProcessTransport; 43 import org.connectbot.util.PreferenceConstants; 44 45 import java.io.IOException; 46 import java.lang.ref.WeakReference; 47 import java.util.List; 48 import java.util.Map; 49 import java.util.concurrent.ConcurrentHashMap; 50 import 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 */ 57 public 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 TerminalManager(ScriptingLayerService service)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 */ disconnectAll()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 */ openConnection(int id)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 */ getConnectedBridge(int id)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 */ closeConnection(TerminalBridge bridge, boolean killProcess)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 Log.i(String.format("Killing process from TerminalManager, %s", intent.toUri(0))); 168 mService.startService(intent); 169 } 170 } 171 if (mDisconnectHandler != null) { 172 Message.obtain(mDisconnectHandler, -1, bridge).sendToTarget(); 173 } 174 } 175 176 /** 177 * Allow {@link TerminalBridge} to resize when the parent has changed. 178 * 179 * @param resizeAllowed 180 */ setResizeAllowed(boolean resizeAllowed)181 public void setResizeAllowed(boolean resizeAllowed) { 182 this.resizeAllowed = resizeAllowed; 183 } 184 isResizeAllowed()185 public boolean isResizeAllowed() { 186 return resizeAllowed; 187 } 188 stop()189 public void stop() { 190 resizeAllowed = false; 191 disconnectAll(); 192 disableMediaPlayer(); 193 } 194 getIntParameter(String key, int defValue)195 public int getIntParameter(String key, int defValue) { 196 return mPreferences.getInt(key, defValue); 197 } 198 getStringParameter(String key, String defValue)199 public String getStringParameter(String key, String defValue) { 200 return mPreferences.getString(key, defValue); 201 } 202 tryKeyVibrate()203 public void tryKeyVibrate() { 204 if (wantKeyVibration) { 205 vibrate(); 206 } 207 } 208 vibrate()209 private void vibrate() { 210 if (vibrator != null) { 211 vibrator.vibrate(VIBRATE_DURATION); 212 } 213 } 214 enableMediaPlayer()215 private void enableMediaPlayer() { 216 mediaPlayer = new MediaPlayer(); 217 218 float volume = 219 mPreferences.getFloat(PreferenceConstants.BELL_VOLUME, 220 PreferenceConstants.DEFAULT_BELL_VOLUME); 221 222 mediaPlayer.setAudioStreamType(AudioManager.STREAM_NOTIFICATION); 223 mediaPlayer.setOnCompletionListener(new BeepListener()); 224 225 AssetFileDescriptor file = mResources.openRawResourceFd(R.raw.bell); 226 try { 227 mediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength()); 228 file.close(); 229 mediaPlayer.setVolume(volume, volume); 230 mediaPlayer.prepare(); 231 } catch (IOException e) { 232 Log.e("Error setting up bell media player", e); 233 } 234 } 235 disableMediaPlayer()236 private void disableMediaPlayer() { 237 if (mediaPlayer != null) { 238 mediaPlayer.release(); 239 mediaPlayer = null; 240 } 241 } 242 playBeep()243 public void playBeep() { 244 if (mediaPlayer != null) { 245 mediaPlayer.start(); 246 } 247 if (wantBellVibration) { 248 vibrate(); 249 } 250 } 251 252 private static class BeepListener implements OnCompletionListener { onCompletion(MediaPlayer mp)253 public void onCompletion(MediaPlayer mp) { 254 mp.seekTo(0); 255 } 256 } 257 isHardKeyboardHidden()258 public boolean isHardKeyboardHidden() { 259 return hardKeyboardHidden; 260 } 261 setHardKeyboardHidden(boolean b)262 public void setHardKeyboardHidden(boolean b) { 263 hardKeyboardHidden = b; 264 } 265 266 @Override onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)267 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 268 if (PreferenceConstants.BELL.equals(key)) { 269 wantAudible = sharedPreferences.getBoolean(PreferenceConstants.BELL, true); 270 if (wantAudible && mediaPlayer == null) { 271 enableMediaPlayer(); 272 } else if (!wantAudible && mediaPlayer != null) { 273 disableMediaPlayer(); 274 } 275 } else if (PreferenceConstants.BELL_VOLUME.equals(key)) { 276 if (mediaPlayer != null) { 277 float volume = 278 sharedPreferences.getFloat(PreferenceConstants.BELL_VOLUME, 279 PreferenceConstants.DEFAULT_BELL_VOLUME); 280 mediaPlayer.setVolume(volume, volume); 281 } 282 } else if (PreferenceConstants.BELL_VIBRATE.equals(key)) { 283 wantBellVibration = sharedPreferences.getBoolean(PreferenceConstants.BELL_VIBRATE, true); 284 } else if (PreferenceConstants.BUMPY_ARROWS.equals(key)) { 285 wantKeyVibration = sharedPreferences.getBoolean(PreferenceConstants.BUMPY_ARROWS, true); 286 } 287 } 288 setDisconnectHandler(Handler disconnectHandler)289 public void setDisconnectHandler(Handler disconnectHandler) { 290 mDisconnectHandler = disconnectHandler; 291 } 292 getBridgeList()293 public List<TerminalBridge> getBridgeList() { 294 return bridges; 295 } 296 getResources()297 public Resources getResources() { 298 return mResources; 299 } 300 registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener)301 public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { 302 mPreferences.registerOnSharedPreferenceChangeListener(listener); 303 } 304 305 } 306