/*
* ConnectBot: simple, powerful, open-source SSH client for Android
* Copyright 2007 Kenny Root, Jeffrey Sharkey
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.connectbot.service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.AssetFileDescriptor;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import com.googlecode.android_scripting.Constants;
import com.googlecode.android_scripting.Log;
import com.googlecode.android_scripting.R;
import com.googlecode.android_scripting.activity.ScriptingLayerService;
import com.googlecode.android_scripting.exception.Sl4aException;
import com.googlecode.android_scripting.interpreter.InterpreterProcess;
import org.connectbot.transport.ProcessTransport;
import org.connectbot.util.PreferenceConstants;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Manager for SSH connections that runs as a background service. This service holds a list of
* currently connected SSH bridges that are ready for connection up to a GUI if needed.
*
*/
public class TerminalManager implements OnSharedPreferenceChangeListener {
private static final long VIBRATE_DURATION = 30;
private final List<TerminalBridge> bridges = new CopyOnWriteArrayList<TerminalBridge>();
private final Map<Integer, WeakReference<TerminalBridge>> mHostBridgeMap =
new ConcurrentHashMap<Integer, WeakReference<TerminalBridge>>();
private Handler mDisconnectHandler = null;
private final Resources mResources;
private final SharedPreferences mPreferences;
private boolean hardKeyboardHidden;
private Vibrator vibrator;
private boolean wantKeyVibration;
private boolean wantBellVibration;
private boolean wantAudible;
private boolean resizeAllowed = false;
private MediaPlayer mediaPlayer;
private final ScriptingLayerService mService;
public TerminalManager(ScriptingLayerService service) {
mService = service;
mPreferences = PreferenceManager.getDefaultSharedPreferences(mService);
registerOnSharedPreferenceChangeListener(this);
mResources = mService.getResources();
hardKeyboardHidden =
(mResources.getConfiguration().hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES);
vibrator = (Vibrator) mService.getSystemService(Context.VIBRATOR_SERVICE);
wantKeyVibration = mPreferences.getBoolean(PreferenceConstants.BUMPY_ARROWS, true);
wantBellVibration = mPreferences.getBoolean(PreferenceConstants.BELL_VIBRATE, true);
wantAudible = mPreferences.getBoolean(PreferenceConstants.BELL, true);
if (wantAudible) {
enableMediaPlayer();
}
}
/**
* Disconnect all currently connected bridges.
*/
private void disconnectAll() {
TerminalBridge[] bridgesArray = null;
if (bridges.size() > 0) {
bridgesArray = bridges.toArray(new TerminalBridge[bridges.size()]);
}
if (bridgesArray != null) {
// disconnect and dispose of any existing bridges
for (TerminalBridge bridge : bridgesArray) {
bridge.dispatchDisconnect(true);
}
}
}
/**
* Open a new session using the given parameters.
*
* @throws InterruptedException
* @throws Sl4aException
*/
public TerminalBridge openConnection(int id) throws IllegalArgumentException, IOException,
InterruptedException, Sl4aException {
// throw exception if terminal already open
if (getConnectedBridge(id) != null) {
throw new IllegalArgumentException("Connection already open");
}
InterpreterProcess process = mService.getProcess(id);
TerminalBridge bridge = new TerminalBridge(this, process, new ProcessTransport(process));
bridge.connect();
WeakReference<TerminalBridge> wr = new WeakReference<TerminalBridge>(bridge);
bridges.add(bridge);
mHostBridgeMap.put(id, wr);
return bridge;
}
/**
* Find a connected {@link TerminalBridge} with the given HostBean.
*
* @param id
* the HostBean to search for
* @return TerminalBridge that uses the HostBean
*/
public TerminalBridge getConnectedBridge(int id) {
WeakReference<TerminalBridge> wr = mHostBridgeMap.get(id);
if (wr != null) {
return wr.get();
} else {
return null;
}
}
/**
* Called by child bridge when somehow it's been disconnected.
*/
public void closeConnection(TerminalBridge bridge, boolean killProcess) {
if (killProcess) {
bridges.remove(bridge);
mHostBridgeMap.remove(bridge.getId());
if (mService.getProcess(bridge.getId()).isAlive()) {
Intent intent = new Intent(mService, mService.getClass());
intent.setAction(Constants.ACTION_KILL_PROCESS);
intent.putExtra(Constants.EXTRA_PROXY_PORT, bridge.getId());
mService.startService(intent);
}
}
if (mDisconnectHandler != null) {
Message.obtain(mDisconnectHandler, -1, bridge).sendToTarget();
}
}
/**
* Allow {@link TerminalBridge} to resize when the parent has changed.
*
* @param resizeAllowed
*/
public void setResizeAllowed(boolean resizeAllowed) {
this.resizeAllowed = resizeAllowed;
}
public boolean isResizeAllowed() {
return resizeAllowed;
}
public void stop() {
resizeAllowed = false;
disconnectAll();
disableMediaPlayer();
}
public int getIntParameter(String key, int defValue) {
return mPreferences.getInt(key, defValue);
}
public String getStringParameter(String key, String defValue) {
return mPreferences.getString(key, defValue);
}
public void tryKeyVibrate() {
if (wantKeyVibration) {
vibrate();
}
}
private void vibrate() {
if (vibrator != null) {
vibrator.vibrate(VIBRATE_DURATION);
}
}
private void enableMediaPlayer() {
mediaPlayer = new MediaPlayer();
float volume =
mPreferences.getFloat(PreferenceConstants.BELL_VOLUME,
PreferenceConstants.DEFAULT_BELL_VOLUME);
mediaPlayer.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);
mediaPlayer.setOnCompletionListener(new BeepListener());
AssetFileDescriptor file = mResources.openRawResourceFd(R.raw.bell);
try {
mediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength());
file.close();
mediaPlayer.setVolume(volume, volume);
mediaPlayer.prepare();
} catch (IOException e) {
Log.e("Error setting up bell media player", e);
}
}
private void disableMediaPlayer() {
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
}
public void playBeep() {
if (mediaPlayer != null) {
mediaPlayer.start();
}
if (wantBellVibration) {
vibrate();
}
}
private static class BeepListener implements OnCompletionListener {
public void onCompletion(MediaPlayer mp) {
mp.seekTo(0);
}
}
public boolean isHardKeyboardHidden() {
return hardKeyboardHidden;
}
public void setHardKeyboardHidden(boolean b) {
hardKeyboardHidden = b;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PreferenceConstants.BELL.equals(key)) {
wantAudible = sharedPreferences.getBoolean(PreferenceConstants.BELL, true);
if (wantAudible && mediaPlayer == null) {
enableMediaPlayer();
} else if (!wantAudible && mediaPlayer != null) {
disableMediaPlayer();
}
} else if (PreferenceConstants.BELL_VOLUME.equals(key)) {
if (mediaPlayer != null) {
float volume =
sharedPreferences.getFloat(PreferenceConstants.BELL_VOLUME,
PreferenceConstants.DEFAULT_BELL_VOLUME);
mediaPlayer.setVolume(volume, volume);
}
} else if (PreferenceConstants.BELL_VIBRATE.equals(key)) {
wantBellVibration = sharedPreferences.getBoolean(PreferenceConstants.BELL_VIBRATE, true);
} else if (PreferenceConstants.BUMPY_ARROWS.equals(key)) {
wantKeyVibration = sharedPreferences.getBoolean(PreferenceConstants.BUMPY_ARROWS, true);
}
}
public void setDisconnectHandler(Handler disconnectHandler) {
mDisconnectHandler = disconnectHandler;
}
public List<TerminalBridge> getBridgeList() {
return bridges;
}
public Resources getResources() {
return mResources;
}
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
mPreferences.registerOnSharedPreferenceChangeListener(listener);
}
}