2 * Copyright (C) 2007 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package jackpal.androidterm;
19 import java.io.UnsupportedEncodingException;
20 import java.util.ArrayList;
22 import android.app.Activity;
23 import android.app.AlertDialog;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.SharedPreferences;
28 import android.content.ServiceConnection;
29 import android.content.res.Configuration;
30 import android.content.res.Resources;
31 import android.net.Uri;
32 import android.net.wifi.WifiManager;
33 import android.os.Bundle;
34 import android.os.IBinder;
35 import android.os.PowerManager;
36 import android.preference.PreferenceManager;
37 import android.text.ClipboardManager;
38 import android.util.DisplayMetrics;
39 import android.util.Log;
40 import android.view.ContextMenu;
41 import android.view.ContextMenu.ContextMenuInfo;
42 import android.view.Gravity;
43 import android.view.KeyEvent;
44 import android.view.Menu;
45 import android.view.MenuItem;
46 import android.view.MotionEvent;
47 import android.view.View;
48 import android.view.Window;
49 import android.view.WindowManager;
50 import android.view.inputmethod.InputMethodManager;
51 import android.widget.FrameLayout;
52 import android.widget.Toast;
54 import jackpal.androidterm.session.TermSession;
55 import jackpal.androidterm.util.TermSettings;
58 * A terminal emulator activity.
61 public class Term extends Activity {
63 * The ViewFlipper which holds the collection of EmulatorView widgets.
65 private TermViewFlipper mViewFlipper;
68 * The name of the ViewFlipper in the resources.
70 private static final int VIEW_FLIPPER = R.id.view_flipper;
72 private ArrayList<TermSession> mTermSessions;
74 private SharedPreferences mPrefs;
75 private TermSettings mSettings;
77 private final static int SELECT_TEXT_ID = 0;
78 private final static int COPY_ALL_ID = 1;
79 private final static int PASTE_ID = 2;
81 private boolean mAlreadyStarted = false;
82 private boolean mStopServiceOnFinish = false;
84 private Intent TSIntent;
86 public static final int REQUEST_CHOOSE_WINDOW = 1;
87 public static final String EXTRA_WINDOW_ID = "jackpal.androidterm.window_id";
88 private int onResumeSelectWindow = -1;
90 private PowerManager.WakeLock mWakeLock;
91 private WifiManager.WifiLock mWifiLock;
93 private boolean mBackKeyPressed;
95 private TermService mTermService;
96 private ServiceConnection mTSConnection = new ServiceConnection() {
97 public void onServiceConnected(ComponentName className, IBinder service) {
98 Log.i(TermDebug.LOG_TAG, "Bound to TermService");
99 TermService.TSBinder binder = (TermService.TSBinder) service;
100 mTermService = binder.getService();
101 populateViewFlipper();
104 public void onServiceDisconnected(ComponentName arg0) {
110 public void onCreate(Bundle icicle) {
111 super.onCreate(icicle);
112 Log.e(TermDebug.LOG_TAG, "onCreate");
113 mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
114 mSettings = new TermSettings(mPrefs);
116 TSIntent = new Intent(this, TermService.class);
117 startService(TSIntent);
119 if (!bindService(TSIntent, mTSConnection, BIND_AUTO_CREATE)) {
120 Log.w(TermDebug.LOG_TAG, "bind to service failed!");
123 setContentView(R.layout.term_activity);
124 mViewFlipper = (TermViewFlipper) findViewById(VIEW_FLIPPER);
126 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
127 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TermDebug.LOG_TAG);
128 WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE);
129 mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL, TermDebug.LOG_TAG);
132 mAlreadyStarted = true;
135 private void populateViewFlipper() {
136 if (mTermService != null) {
137 mTermSessions = mTermService.getSessions();
139 if (mTermSessions.size() == 0) {
140 mTermSessions.add(createTermSession());
143 for (TermSession session : mTermSessions) {
144 EmulatorView view = createEmulatorView(session);
145 mViewFlipper.addView(view);
153 public void onDestroy() {
155 mViewFlipper.removeAllViews();
156 unbindService(mTSConnection);
157 if (mStopServiceOnFinish) {
158 stopService(TSIntent);
161 mTSConnection = null;
162 if (mWakeLock.isHeld()) {
165 if (mWifiLock.isHeld()) {
170 private void restart() {
171 startActivity(getIntent());
175 private TermSession createTermSession() {
176 /* Check whether we've received an initial command from the
177 * launching application
179 String initialCommand = mSettings.getInitialCommand();
180 String iInitialCommand = getIntent().getStringExtra("jackpal.androidterm.iInitialCommand");
181 if (iInitialCommand != null) {
182 if (initialCommand != null) {
183 initialCommand += "\r" + iInitialCommand;
185 initialCommand = iInitialCommand;
189 return new TermSession(mSettings, null, initialCommand);
192 private EmulatorView createEmulatorView(TermSession session) {
193 DisplayMetrics metrics = new DisplayMetrics();
194 getWindowManager().getDefaultDisplay().getMetrics(metrics);
195 EmulatorView emulatorView = new EmulatorView(this, session, mViewFlipper, metrics);
197 FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
198 FrameLayout.LayoutParams.WRAP_CONTENT,
199 FrameLayout.LayoutParams.WRAP_CONTENT,
202 emulatorView.setLayoutParams(params);
204 session.setUpdateCallback(emulatorView.getUpdateCallback());
206 registerForContextMenu(emulatorView);
211 private TermSession getCurrentTermSession() {
212 return mTermSessions.get(mViewFlipper.getDisplayedChild());
215 private EmulatorView getCurrentEmulatorView() {
216 return (EmulatorView) mViewFlipper.getCurrentView();
219 private void updatePrefs() {
220 DisplayMetrics metrics = new DisplayMetrics();
221 getWindowManager().getDefaultDisplay().getMetrics(metrics);
223 for (View v : mViewFlipper) {
224 ((EmulatorView) v).setDensity(metrics);
225 ((EmulatorView) v).updatePrefs(mSettings);
227 if (mTermSessions != null) {
228 for (TermSession session : mTermSessions) {
229 session.updatePrefs(mSettings);
233 Window win = getWindow();
234 WindowManager.LayoutParams params = win.getAttributes();
235 final int FULLSCREEN = WindowManager.LayoutParams.FLAG_FULLSCREEN;
236 int desiredFlag = mSettings.showStatusBar() ? 0 : FULLSCREEN;
237 if (desiredFlag != (params.flags & FULLSCREEN)) {
238 if (mAlreadyStarted) {
239 // Can't switch to/from fullscreen after
240 // starting the activity.
243 win.setFlags(desiredFlag, FULLSCREEN);
250 public void onResume() {
253 if (mTermSessions != null && mTermSessions.size() < mViewFlipper.getChildCount()) {
254 for (int i = 0; i < mViewFlipper.getChildCount(); ++i) {
255 EmulatorView v = (EmulatorView) mViewFlipper.getChildAt(i);
256 if (!mTermSessions.contains(v.getTermSession())) {
258 mViewFlipper.removeView(v);
264 mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
265 mSettings.readPrefs(mPrefs);
268 if (onResumeSelectWindow >= 0) {
269 mViewFlipper.setDisplayedChild(onResumeSelectWindow);
270 onResumeSelectWindow = -1;
272 mViewFlipper.resumeCurrentView();
277 public void onPause() {
280 mViewFlipper.pauseCurrentView();
282 /* Explicitly close the input method
283 Otherwise, the soft keyboard could cover up whatever activity takes
285 final IBinder token = mViewFlipper.getWindowToken();
289 InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
290 imm.hideSoftInputFromWindow(token, 0);
296 public void onConfigurationChanged(Configuration newConfig) {
297 super.onConfigurationChanged(newConfig);
299 EmulatorView v = (EmulatorView) mViewFlipper.getCurrentView();
306 public boolean onCreateOptionsMenu(Menu menu) {
307 getMenuInflater().inflate(R.menu.main, menu);
312 public boolean onOptionsItemSelected(MenuItem item) {
313 int id = item.getItemId();
314 if (id == R.id.menu_preferences) {
316 } else if (id == R.id.menu_new_window) {
318 } else if (id == R.id.menu_close_window) {
320 } else if (id == R.id.menu_window_list) {
321 startActivityForResult(new Intent(this, WindowList.class), REQUEST_CHOOSE_WINDOW);
322 } else if (id == R.id.menu_reset) {
324 } else if (id == R.id.menu_send_email) {
326 } else if (id == R.id.menu_special_keys) {
328 } else if (id == R.id.menu_toggle_soft_keyboard) {
329 doToggleSoftKeyboard();
330 } else if (id == R.id.menu_toggle_wakelock) {
332 } else if (id == R.id.menu_toggle_wifilock) {
335 return super.onOptionsItemSelected(item);
338 private void doCreateNewWindow() {
339 if (mTermSessions == null) {
340 Log.w(TermDebug.LOG_TAG, "Couldn't create new window because mTermSessions == null");
344 TermSession session = createTermSession();
345 mTermSessions.add(session);
347 EmulatorView view = createEmulatorView(session);
348 view.updatePrefs(mSettings);
350 mViewFlipper.addView(view);
351 mViewFlipper.setDisplayedChild(mViewFlipper.getChildCount()-1);
354 private void doCloseWindow() {
355 if (mTermSessions == null) {
359 EmulatorView view = getCurrentEmulatorView();
363 TermSession session = mTermSessions.remove(mViewFlipper.getDisplayedChild());
366 mViewFlipper.removeView(view);
367 if (mTermSessions.size() == 0) {
368 mStopServiceOnFinish = true;
371 mViewFlipper.showNext();
376 protected void onActivityResult(int request, int result, Intent data) {
378 case REQUEST_CHOOSE_WINDOW:
379 if (result == RESULT_OK && data != null) {
380 int position = data.getIntExtra(EXTRA_WINDOW_ID, -2);
382 // Switch windows after session list is in sync, not here
383 onResumeSelectWindow = position;
384 } else if (position == -1) {
388 // Close the activity if user closed all sessions
389 if (mTermSessions.size() == 0) {
390 mStopServiceOnFinish = true;
399 public boolean onPrepareOptionsMenu(Menu menu) {
400 MenuItem wakeLockItem = menu.findItem(R.id.menu_toggle_wakelock);
401 MenuItem wifiLockItem = menu.findItem(R.id.menu_toggle_wifilock);
402 if (mWakeLock.isHeld()) {
403 wakeLockItem.setTitle(R.string.disable_wakelock);
405 wakeLockItem.setTitle(R.string.enable_wakelock);
407 if (mWifiLock.isHeld()) {
408 wifiLockItem.setTitle(R.string.disable_wifilock);
410 wifiLockItem.setTitle(R.string.enable_wifilock);
412 return super.onPrepareOptionsMenu(menu);
416 public void onCreateContextMenu(ContextMenu menu, View v,
417 ContextMenuInfo menuInfo) {
418 super.onCreateContextMenu(menu, v, menuInfo);
419 menu.setHeaderTitle(R.string.edit_text);
420 menu.add(0, SELECT_TEXT_ID, 0, R.string.select_text);
421 menu.add(0, COPY_ALL_ID, 0, R.string.copy_all);
422 menu.add(0, PASTE_ID, 0, R.string.paste);
424 menu.getItem(PASTE_ID).setEnabled(false);
429 public boolean onContextItemSelected(MenuItem item) {
430 switch (item.getItemId()) {
432 getCurrentEmulatorView().toggleSelectingText();
441 return super.onContextItemSelected(item);
446 public boolean onKeyDown(int keyCode, KeyEvent event) {
447 /* The pre-Eclair default implementation of onKeyDown() would prevent
448 our handling of the Back key in onKeyUp() from taking effect, so
450 if (keyCode == KeyEvent.KEYCODE_BACK) {
451 /* Android pre-Eclair has no key event tracking, and a back key
452 down event delivered to an activity above us in the back stack
453 could be succeeded by a back key up event to us, so we need to
454 keep track of our own back key presses */
455 mBackKeyPressed = true;
458 return super.onKeyDown(keyCode, event);
463 public boolean onKeyUp(int keyCode, KeyEvent event) {
465 case KeyEvent.KEYCODE_BACK:
466 if (!mBackKeyPressed) {
467 /* This key up event might correspond to a key down delivered
468 to another activity -- ignore */
471 mBackKeyPressed = false;
472 switch (mSettings.getBackKeyAction()) {
473 case TermSettings.BACK_KEY_STOPS_SERVICE:
474 mStopServiceOnFinish = true;
475 case TermSettings.BACK_KEY_CLOSES_ACTIVITY:
478 case TermSettings.BACK_KEY_CLOSES_WINDOW:
485 return super.onKeyUp(keyCode, event);
489 private boolean canPaste() {
490 ClipboardManager clip = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
491 if (clip.hasText()) {
497 private void doPreferences() {
498 startActivity(new Intent(this, TermPreferences.class));
501 private void doResetTerminal() {
502 TermSession session = getCurrentTermSession();
503 if (session != null) {
508 private void doEmailTranscript() {
509 // Don't really want to supply an address, but
510 // currently it's required, otherwise we get an
512 String addr = "user@example.com";
514 new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"
517 intent.putExtra("body", getCurrentTermSession().getTranscriptText().trim());
518 startActivity(intent);
521 private void doCopyAll() {
522 ClipboardManager clip = (ClipboardManager)
523 getSystemService(Context.CLIPBOARD_SERVICE);
524 clip.setText(getCurrentTermSession().getTranscriptText().trim());
527 private void doPaste() {
528 ClipboardManager clip = (ClipboardManager)
529 getSystemService(Context.CLIPBOARD_SERVICE);
530 CharSequence paste = clip.getText();
533 utf8 = paste.toString().getBytes("UTF-8");
534 } catch (UnsupportedEncodingException e) {
535 Log.e(TermDebug.LOG_TAG, "UTF-8 encoding not found.");
538 getCurrentTermSession().write(paste.toString());
541 private void doDocumentKeys() {
542 AlertDialog.Builder dialog = new AlertDialog.Builder(this);
543 Resources r = getResources();
544 dialog.setTitle(r.getString(R.string.control_key_dialog_title));
546 formatMessage(mSettings.getControlKeyId(), TermSettings.CONTROL_KEY_ID_NONE,
547 r, R.array.control_keys_short_names,
548 R.string.control_key_dialog_control_text,
549 R.string.control_key_dialog_control_disabled_text, "CTRLKEY")
551 formatMessage(mSettings.getFnKeyId(), TermSettings.FN_KEY_ID_NONE,
552 r, R.array.fn_keys_short_names,
553 R.string.control_key_dialog_fn_text,
554 R.string.control_key_dialog_fn_disabled_text, "FNKEY"));
558 private String formatMessage(int keyId, int disabledKeyId,
559 Resources r, int arrayId,
561 int disabledId, String regex) {
562 if (keyId == disabledKeyId) {
563 return r.getString(disabledId);
565 String[] keyNames = r.getStringArray(arrayId);
566 String keyName = keyNames[keyId];
567 String template = r.getString(enabledId);
568 String result = template.replaceAll(regex, keyName);
572 private void doToggleSoftKeyboard() {
573 InputMethodManager imm = (InputMethodManager)
574 getSystemService(Context.INPUT_METHOD_SERVICE);
575 imm.toggleSoftInput(InputMethodManager.SHOW_FORCED,0);
579 private void doToggleWakeLock() {
580 if (mWakeLock.isHeld()) {
587 private void doToggleWifiLock() {
588 if (mWifiLock.isHeld()) {