• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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