Home | History | Annotate | Download | only in service
      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 
     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    */
    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    */
    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    */
    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    */
    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    */
    182   public void setResizeAllowed(boolean resizeAllowed) {
    183     this.resizeAllowed = resizeAllowed;
    184   }
    185 
    186   public boolean isResizeAllowed() {
    187     return resizeAllowed;
    188   }
    189 
    190   public void stop() {
    191     resizeAllowed = false;
    192     disconnectAll();
    193     disableMediaPlayer();
    194   }
    195 
    196   public int getIntParameter(String key, int defValue) {
    197     return mPreferences.getInt(key, defValue);
    198   }
    199 
    200   public String getStringParameter(String key, String defValue) {
    201     return mPreferences.getString(key, defValue);
    202   }
    203 
    204   public void tryKeyVibrate() {
    205     if (wantKeyVibration) {
    206       vibrate();
    207     }
    208   }
    209 
    210   private void vibrate() {
    211     if (vibrator != null) {
    212       vibrator.vibrate(VIBRATE_DURATION);
    213     }
    214   }
    215 
    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 
    237   private void disableMediaPlayer() {
    238     if (mediaPlayer != null) {
    239       mediaPlayer.release();
    240       mediaPlayer = null;
    241     }
    242   }
    243 
    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 {
    254     public void onCompletion(MediaPlayer mp) {
    255       mp.seekTo(0);
    256     }
    257   }
    258 
    259   public boolean isHardKeyboardHidden() {
    260     return hardKeyboardHidden;
    261   }
    262 
    263   public void setHardKeyboardHidden(boolean b) {
    264     hardKeyboardHidden = b;
    265   }
    266 
    267   @Override
    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 
    290   public void setDisconnectHandler(Handler disconnectHandler) {
    291     mDisconnectHandler = disconnectHandler;
    292   }
    293 
    294   public List<TerminalBridge> getBridgeList() {
    295     return bridges;
    296   }
    297 
    298   public Resources getResources() {
    299     return mResources;
    300   }
    301 
    302   public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
    303     mPreferences.registerOnSharedPreferenceChangeListener(listener);
    304   }
    305 
    306 }
    307