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.Menu;
44 import android.view.MenuItem;
45 import android.view.MotionEvent;
46 import android.view.View;
47 import android.view.Window;
48 import android.view.WindowManager;
49 import android.view.inputmethod.InputMethodManager;
50 import android.widget.FrameLayout;
51 import android.widget.Toast;
53 import jackpal.androidterm.session.TermSession;
54 import jackpal.androidterm.util.TermSettings;
57 * A terminal emulator activity.
60 public class Term extends Activity {
62 * The ViewFlipper which holds the collection of EmulatorView widgets.
64 private TermViewFlipper mViewFlipper;
67 * The name of the ViewFlipper in the resources.
69 private static final int VIEW_FLIPPER = R.id.view_flipper;
71 private ArrayList<TermSession> mTermSessions;
73 private SharedPreferences mPrefs;
74 private TermSettings mSettings;
76 private final static int SELECT_TEXT_ID = 0;
77 private final static int COPY_ALL_ID = 1;
78 private final static int PASTE_ID = 2;
80 private boolean mAlreadyStarted = false;
82 private Intent TSIntent;
84 public static final int REQUEST_CHOOSE_WINDOW = 1;
85 public static final String EXTRA_WINDOW_ID = "jackpal.androidterm.window_id";
86 private int onResumeSelectWindow = -1;
88 private PowerManager.WakeLock mWakeLock;
89 private WifiManager.WifiLock mWifiLock;
91 private TermService mTermService;
92 private ServiceConnection mTSConnection = new ServiceConnection() {
93 public void onServiceConnected(ComponentName className, IBinder service) {
94 Log.i(TermDebug.LOG_TAG, "Bound to TermService");
95 TermService.TSBinder binder = (TermService.TSBinder) service;
96 mTermService = binder.getService();
97 populateViewFlipper();
100 public void onServiceDisconnected(ComponentName arg0) {
106 public void onCreate(Bundle icicle) {
107 super.onCreate(icicle);
108 Log.e(TermDebug.LOG_TAG, "onCreate");
109 mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
110 mSettings = new TermSettings(mPrefs);
112 TSIntent = new Intent(this, TermService.class);
113 startService(TSIntent);
115 if (!bindService(TSIntent, mTSConnection, BIND_AUTO_CREATE)) {
116 Log.w(TermDebug.LOG_TAG, "bind to service failed!");
119 setContentView(R.layout.term_activity);
120 mViewFlipper = (TermViewFlipper) findViewById(VIEW_FLIPPER);
122 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
123 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TermDebug.LOG_TAG);
124 WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE);
125 mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL, TermDebug.LOG_TAG);
128 mAlreadyStarted = true;
131 private void populateViewFlipper() {
132 if (mTermService != null) {
133 mTermSessions = mTermService.getSessions();
135 if (mTermSessions.size() == 0) {
136 mTermSessions.add(createTermSession());
139 for (TermSession session : mTermSessions) {
140 EmulatorView view = createEmulatorView(session);
141 mViewFlipper.addView(view);
149 public void onDestroy() {
151 mViewFlipper.removeAllViews();
152 unbindService(mTSConnection);
153 stopService(TSIntent);
155 mTSConnection = null;
156 if (mWakeLock.isHeld()) {
159 if (mWifiLock.isHeld()) {
164 private void restart() {
165 startActivity(getIntent());
169 private TermSession createTermSession() {
170 /* Check whether we've received an initial command from the
171 * launching application
173 String initialCommand = mSettings.getInitialCommand();
174 String iInitialCommand = getIntent().getStringExtra("jackpal.androidterm.iInitialCommand");
175 if (iInitialCommand != null) {
176 if (initialCommand != null) {
177 initialCommand += "\r" + iInitialCommand;
179 initialCommand = iInitialCommand;
183 return new TermSession(mSettings, null, initialCommand);
186 private EmulatorView createEmulatorView(TermSession session) {
187 DisplayMetrics metrics = new DisplayMetrics();
188 getWindowManager().getDefaultDisplay().getMetrics(metrics);
189 EmulatorView emulatorView = new EmulatorView(this, session, mViewFlipper, metrics);
191 FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
192 FrameLayout.LayoutParams.WRAP_CONTENT,
193 FrameLayout.LayoutParams.WRAP_CONTENT,
196 emulatorView.setLayoutParams(params);
198 session.setUpdateCallback(emulatorView.getUpdateCallback());
200 registerForContextMenu(emulatorView);
205 private TermSession getCurrentTermSession() {
206 return mTermSessions.get(mViewFlipper.getDisplayedChild());
209 private EmulatorView getCurrentEmulatorView() {
210 return (EmulatorView) mViewFlipper.getCurrentView();
213 private void updatePrefs() {
214 DisplayMetrics metrics = new DisplayMetrics();
215 getWindowManager().getDefaultDisplay().getMetrics(metrics);
217 for (View v : mViewFlipper) {
218 ((EmulatorView) v).setDensity(metrics);
219 ((EmulatorView) v).updatePrefs(mSettings);
221 if (mTermSessions != null) {
222 for (TermSession session : mTermSessions) {
223 session.updatePrefs(mSettings);
227 Window win = getWindow();
228 WindowManager.LayoutParams params = win.getAttributes();
229 final int FULLSCREEN = WindowManager.LayoutParams.FLAG_FULLSCREEN;
230 int desiredFlag = mSettings.showStatusBar() ? 0 : FULLSCREEN;
231 if (desiredFlag != (params.flags & FULLSCREEN)) {
232 if (mAlreadyStarted) {
233 // Can't switch to/from fullscreen after
234 // starting the activity.
237 win.setFlags(desiredFlag, FULLSCREEN);
244 public void onResume() {
247 if (mTermSessions != null && mTermSessions.size() < mViewFlipper.getChildCount()) {
248 for (int i = 0; i < mViewFlipper.getChildCount(); ++i) {
249 EmulatorView v = (EmulatorView) mViewFlipper.getChildAt(i);
250 if (!mTermSessions.contains(v.getTermSession())) {
252 mViewFlipper.removeView(v);
258 mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
259 mSettings.readPrefs(mPrefs);
262 if (onResumeSelectWindow >= 0) {
263 mViewFlipper.setDisplayedChild(onResumeSelectWindow);
264 onResumeSelectWindow = -1;
266 mViewFlipper.resumeCurrentView();
271 public void onPause() {
274 mViewFlipper.pauseCurrentView();
276 /* Explicitly close the input method
277 Otherwise, the soft keyboard could cover up whatever activity takes
279 final IBinder token = mViewFlipper.getWindowToken();
283 InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
284 imm.hideSoftInputFromWindow(token, 0);
290 public void onConfigurationChanged(Configuration newConfig) {
291 super.onConfigurationChanged(newConfig);
293 EmulatorView v = (EmulatorView) mViewFlipper.getCurrentView();
300 public boolean onCreateOptionsMenu(Menu menu) {
301 getMenuInflater().inflate(R.menu.main, menu);
306 public boolean onOptionsItemSelected(MenuItem item) {
307 int id = item.getItemId();
308 if (id == R.id.menu_preferences) {
310 } else if (id == R.id.menu_new_window) {
312 } else if (id == R.id.menu_close_window) {
314 } else if (id == R.id.menu_window_list) {
315 startActivityForResult(new Intent(this, WindowList.class), REQUEST_CHOOSE_WINDOW);
316 } else if (id == R.id.menu_reset) {
318 } else if (id == R.id.menu_send_email) {
320 } else if (id == R.id.menu_special_keys) {
322 } else if (id == R.id.menu_toggle_soft_keyboard) {
323 doToggleSoftKeyboard();
324 } else if (id == R.id.menu_toggle_wakelock) {
326 } else if (id == R.id.menu_toggle_wifilock) {
329 return super.onOptionsItemSelected(item);
332 private void doCreateNewWindow() {
333 if (mTermSessions == null) {
334 Log.w(TermDebug.LOG_TAG, "Couldn't create new window because mTermSessions == null");
338 TermSession session = createTermSession();
339 mTermSessions.add(session);
341 EmulatorView view = createEmulatorView(session);
342 view.updatePrefs(mSettings);
344 mViewFlipper.addView(view);
345 mViewFlipper.setDisplayedChild(mViewFlipper.getChildCount()-1);
348 private void doCloseWindow() {
349 if (mTermSessions == null) {
353 EmulatorView view = getCurrentEmulatorView();
357 TermSession session = mTermSessions.remove(mViewFlipper.getDisplayedChild());
360 mViewFlipper.removeView(view);
361 if (mTermSessions.size() == 0) {
364 mViewFlipper.showNext();
369 protected void onActivityResult(int request, int result, Intent data) {
371 case REQUEST_CHOOSE_WINDOW:
372 if (result == RESULT_OK && data != null) {
373 int position = data.getIntExtra(EXTRA_WINDOW_ID, -2);
375 // Switch windows after session list is in sync, not here
376 onResumeSelectWindow = position;
377 } else if (position == -1) {
381 // Close the activity if user closed all sessions
382 if (mTermSessions.size() == 0) {
391 public boolean onPrepareOptionsMenu(Menu menu) {
392 MenuItem wakeLockItem = menu.findItem(R.id.menu_toggle_wakelock);
393 MenuItem wifiLockItem = menu.findItem(R.id.menu_toggle_wifilock);
394 if (mWakeLock.isHeld()) {
395 wakeLockItem.setTitle(R.string.disable_wakelock);
397 wakeLockItem.setTitle(R.string.enable_wakelock);
399 if (mWifiLock.isHeld()) {
400 wifiLockItem.setTitle(R.string.disable_wifilock);
402 wifiLockItem.setTitle(R.string.enable_wifilock);
404 return super.onPrepareOptionsMenu(menu);
408 public void onCreateContextMenu(ContextMenu menu, View v,
409 ContextMenuInfo menuInfo) {
410 super.onCreateContextMenu(menu, v, menuInfo);
411 menu.setHeaderTitle(R.string.edit_text);
412 menu.add(0, SELECT_TEXT_ID, 0, R.string.select_text);
413 menu.add(0, COPY_ALL_ID, 0, R.string.copy_all);
414 menu.add(0, PASTE_ID, 0, R.string.paste);
416 menu.getItem(PASTE_ID).setEnabled(false);
421 public boolean onContextItemSelected(MenuItem item) {
422 switch (item.getItemId()) {
424 getCurrentEmulatorView().toggleSelectingText();
433 return super.onContextItemSelected(item);
437 private boolean canPaste() {
438 ClipboardManager clip = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
439 if (clip.hasText()) {
445 private void doPreferences() {
446 startActivity(new Intent(this, TermPreferences.class));
449 private void doResetTerminal() {
453 private void doEmailTranscript() {
454 // Don't really want to supply an address, but
455 // currently it's required, otherwise we get an
457 String addr = "user@example.com";
459 new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"
462 intent.putExtra("body", getCurrentTermSession().getTranscriptText().trim());
463 startActivity(intent);
466 private void doCopyAll() {
467 ClipboardManager clip = (ClipboardManager)
468 getSystemService(Context.CLIPBOARD_SERVICE);
469 clip.setText(getCurrentTermSession().getTranscriptText().trim());
472 private void doPaste() {
473 ClipboardManager clip = (ClipboardManager)
474 getSystemService(Context.CLIPBOARD_SERVICE);
475 CharSequence paste = clip.getText();
478 utf8 = paste.toString().getBytes("UTF-8");
479 } catch (UnsupportedEncodingException e) {
480 Log.e(TermDebug.LOG_TAG, "UTF-8 encoding not found.");
483 getCurrentTermSession().write(paste.toString());
486 private void doDocumentKeys() {
487 AlertDialog.Builder dialog = new AlertDialog.Builder(this);
488 Resources r = getResources();
489 dialog.setTitle(r.getString(R.string.control_key_dialog_title));
491 formatMessage(mSettings.getControlKeyId(), TermSettings.CONTROL_KEY_ID_NONE,
492 r, R.array.control_keys_short_names,
493 R.string.control_key_dialog_control_text,
494 R.string.control_key_dialog_control_disabled_text, "CTRLKEY")
496 formatMessage(mSettings.getFnKeyId(), TermSettings.FN_KEY_ID_NONE,
497 r, R.array.fn_keys_short_names,
498 R.string.control_key_dialog_fn_text,
499 R.string.control_key_dialog_fn_disabled_text, "FNKEY"));
503 private String formatMessage(int keyId, int disabledKeyId,
504 Resources r, int arrayId,
506 int disabledId, String regex) {
507 if (keyId == disabledKeyId) {
508 return r.getString(disabledId);
510 String[] keyNames = r.getStringArray(arrayId);
511 String keyName = keyNames[keyId];
512 String template = r.getString(enabledId);
513 String result = template.replaceAll(regex, keyName);
517 private void doToggleSoftKeyboard() {
518 InputMethodManager imm = (InputMethodManager)
519 getSystemService(Context.INPUT_METHOD_SERVICE);
520 imm.toggleSoftInput(InputMethodManager.SHOW_FORCED,0);
524 private void doToggleWakeLock() {
525 if (mWakeLock.isHeld()) {
532 private void doToggleWifiLock() {
533 if (mWifiLock.isHeld()) {