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.activity.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 * @author jsharkey 57 * @author modified by raaar 58 */ 59 public class TerminalManager implements OnSharedPreferenceChangeListener { 60 61 private static final long VIBRATE_DURATION = 30; 62 63 private final List<TerminalBridge> bridges = new CopyOnWriteArrayList<TerminalBridge>(); 64 65 private final Map<Integer, WeakReference<TerminalBridge>> mHostBridgeMap = 66 new ConcurrentHashMap<Integer, WeakReference<TerminalBridge>>(); 67 68 private Handler mDisconnectHandler = null; 69 70 private final Resources mResources; 71 72 private final SharedPreferences mPreferences; 73 74 private boolean hardKeyboardHidden; 75 76 private Vibrator vibrator; 77 private boolean wantKeyVibration; 78 private boolean wantBellVibration; 79 private boolean wantAudible; 80 private boolean resizeAllowed = false; 81 private MediaPlayer mediaPlayer; 82 83 private final ScriptingLayerService mService; 84 TerminalManager(ScriptingLayerService service)85 public TerminalManager(ScriptingLayerService service) { 86 mService = service; 87 mPreferences = PreferenceManager.getDefaultSharedPreferences(mService); 88 registerOnSharedPreferenceChangeListener(this); 89 mResources = mService.getResources(); 90 hardKeyboardHidden = 91 (mResources.getConfiguration().hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES); 92 vibrator = (Vibrator) mService.getSystemService(Context.VIBRATOR_SERVICE); 93 wantKeyVibration = mPreferences.getBoolean(PreferenceConstants.BUMPY_ARROWS, true); 94 wantBellVibration = mPreferences.getBoolean(PreferenceConstants.BELL_VIBRATE, true); 95 wantAudible = mPreferences.getBoolean(PreferenceConstants.BELL, true); 96 if (wantAudible) { 97 enableMediaPlayer(); 98 } 99 } 100 101 /** 102 * Disconnect all currently connected bridges. 103 */ disconnectAll()104 private void disconnectAll() { 105 TerminalBridge[] bridgesArray = null; 106 if (bridges.size() > 0) { 107 bridgesArray = bridges.toArray(new TerminalBridge[bridges.size()]); 108 } 109 if (bridgesArray != null) { 110 // disconnect and dispose of any existing bridges 111 for (TerminalBridge bridge : bridgesArray) { 112 bridge.dispatchDisconnect(true); 113 } 114 } 115 } 116 117 /** 118 * Open a new session using the given parameters. 119 * 120 * @throws InterruptedException 121 * @throws Sl4aException 122 */ openConnection(int id)123 public TerminalBridge openConnection(int id) throws IllegalArgumentException, IOException, 124 InterruptedException, Sl4aException { 125 // throw exception if terminal already open 126 if (getConnectedBridge(id) != null) { 127 throw new IllegalArgumentException("Connection already open"); 128 } 129 130 InterpreterProcess process = mService.getProcess(id); 131 132 TerminalBridge bridge = new TerminalBridge(this, process, new ProcessTransport(process)); 133 bridge.connect(); 134 135 WeakReference<TerminalBridge> wr = new WeakReference<TerminalBridge>(bridge); 136 bridges.add(bridge); 137 mHostBridgeMap.put(id, wr); 138 139 return bridge; 140 } 141 142 /** 143 * Find a connected {@link TerminalBridge} with the given HostBean. 144 * 145 * @param id 146 * the HostBean to search for 147 * @return TerminalBridge that uses the HostBean 148 */ getConnectedBridge(int id)149 public TerminalBridge getConnectedBridge(int id) { 150 WeakReference<TerminalBridge> wr = mHostBridgeMap.get(id); 151 if (wr != null) { 152 return wr.get(); 153 } else { 154 return null; 155 } 156 } 157 158 /** 159 * Called by child bridge when somehow it's been disconnected. 160 */ closeConnection(TerminalBridge bridge, boolean killProcess)161 public void closeConnection(TerminalBridge bridge, boolean killProcess) { 162 if (killProcess) { 163 bridges.remove(bridge); 164 mHostBridgeMap.remove(bridge.getId()); 165 if (mService.getProcess(bridge.getId()).isAlive()) { 166 Intent intent = new Intent(mService, mService.getClass()); 167 intent.setAction(Constants.ACTION_KILL_PROCESS); 168 intent.putExtra(Constants.EXTRA_PROXY_PORT, bridge.getId()); 169 mService.startService(intent); 170 } 171 } 172 if (mDisconnectHandler != null) { 173 Message.obtain(mDisconnectHandler, -1, bridge).sendToTarget(); 174 } 175 } 176 177 /** 178 * Allow {@link TerminalBridge} to resize when the parent has changed. 179 * 180 * @param resizeAllowed 181 */ setResizeAllowed(boolean resizeAllowed)182 public void setResizeAllowed(boolean resizeAllowed) { 183 this.resizeAllowed = resizeAllowed; 184 } 185 isResizeAllowed()186 public boolean isResizeAllowed() { 187 return resizeAllowed; 188 } 189 stop()190 public void stop() { 191 resizeAllowed = false; 192 disconnectAll(); 193 disableMediaPlayer(); 194 } 195 getIntParameter(String key, int defValue)196 public int getIntParameter(String key, int defValue) { 197 return mPreferences.getInt(key, defValue); 198 } 199 getStringParameter(String key, String defValue)200 public String getStringParameter(String key, String defValue) { 201 return mPreferences.getString(key, defValue); 202 } 203 tryKeyVibrate()204 public void tryKeyVibrate() { 205 if (wantKeyVibration) { 206 vibrate(); 207 } 208 } 209 vibrate()210 private void vibrate() { 211 if (vibrator != null) { 212 vibrator.vibrate(VIBRATE_DURATION); 213 } 214 } 215 enableMediaPlayer()216 private void enableMediaPlayer() { 217 mediaPlayer = new MediaPlayer(); 218 219 float volume = 220 mPreferences.getFloat(PreferenceConstants.BELL_VOLUME, 221 PreferenceConstants.DEFAULT_BELL_VOLUME); 222 223 mediaPlayer.setAudioStreamType(AudioManager.STREAM_NOTIFICATION); 224 mediaPlayer.setOnCompletionListener(new BeepListener()); 225 226 AssetFileDescriptor file = mResources.openRawResourceFd(R.raw.bell); 227 try { 228 mediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength()); 229 file.close(); 230 mediaPlayer.setVolume(volume, volume); 231 mediaPlayer.prepare(); 232 } catch (IOException e) { 233 Log.e("Error setting up bell media player", e); 234 } 235 } 236 disableMediaPlayer()237 private void disableMediaPlayer() { 238 if (mediaPlayer != null) { 239 mediaPlayer.release(); 240 mediaPlayer = null; 241 } 242 } 243 playBeep()244 public void playBeep() { 245 if (mediaPlayer != null) { 246 mediaPlayer.start(); 247 } 248 if (wantBellVibration) { 249 vibrate(); 250 } 251 } 252 253 private static class BeepListener implements OnCompletionListener { onCompletion(MediaPlayer mp)254 public void onCompletion(MediaPlayer mp) { 255 mp.seekTo(0); 256 } 257 } 258 isHardKeyboardHidden()259 public boolean isHardKeyboardHidden() { 260 return hardKeyboardHidden; 261 } 262 setHardKeyboardHidden(boolean b)263 public void setHardKeyboardHidden(boolean b) { 264 hardKeyboardHidden = b; 265 } 266 267 @Override onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)268 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 269 if (PreferenceConstants.BELL.equals(key)) { 270 wantAudible = sharedPreferences.getBoolean(PreferenceConstants.BELL, true); 271 if (wantAudible && mediaPlayer == null) { 272 enableMediaPlayer(); 273 } else if (!wantAudible && mediaPlayer != null) { 274 disableMediaPlayer(); 275 } 276 } else if (PreferenceConstants.BELL_VOLUME.equals(key)) { 277 if (mediaPlayer != null) { 278 float volume = 279 sharedPreferences.getFloat(PreferenceConstants.BELL_VOLUME, 280 PreferenceConstants.DEFAULT_BELL_VOLUME); 281 mediaPlayer.setVolume(volume, volume); 282 } 283 } else if (PreferenceConstants.BELL_VIBRATE.equals(key)) { 284 wantBellVibration = sharedPreferences.getBoolean(PreferenceConstants.BELL_VIBRATE, true); 285 } else if (PreferenceConstants.BUMPY_ARROWS.equals(key)) { 286 wantKeyVibration = sharedPreferences.getBoolean(PreferenceConstants.BUMPY_ARROWS, true); 287 } 288 } 289 setDisconnectHandler(Handler disconnectHandler)290 public void setDisconnectHandler(Handler disconnectHandler) { 291 mDisconnectHandler = disconnectHandler; 292 } 293 getBridgeList()294 public List<TerminalBridge> getBridgeList() { 295 return bridges; 296 } 297 getResources()298 public Resources getResources() { 299 return mResources; 300 } 301 registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener)302 public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { 303 mPreferences.registerOnSharedPreferenceChangeListener(listener); 304 } 305 306 } 307