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.FileDescriptor;
20 import java.io.FileOutputStream;
21 import java.io.IOException;
22 import java.io.UnsupportedEncodingException;
23 import java.util.ArrayList;
25 import android.app.Activity;
26 import android.app.AlertDialog;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.SharedPreferences;
30 import android.content.res.Configuration;
31 import android.content.res.Resources;
32 import android.net.Uri;
33 import android.net.wifi.WifiManager;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.Message;
37 import android.os.PowerManager;
38 import android.preference.PreferenceManager;
39 import android.text.ClipboardManager;
40 import android.util.DisplayMetrics;
41 import android.util.Log;
42 import android.view.ContextMenu;
43 import android.view.ContextMenu.ContextMenuInfo;
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;
52 import jackpal.androidterm.util.TermSettings;
55 * A terminal emulator activity.
58 public class Term extends Activity {
60 * Our main view. Displays the emulated terminal screen.
62 private EmulatorView mEmulatorView;
65 * The pseudo-teletype (pty) file descriptor that we use to communicate with
66 * another process, typically a shell.
68 private FileDescriptor mTermFd;
71 * Used to send data to the remote process.
73 private FileOutputStream mTermOut;
76 * The process ID of the remote process.
78 private int mProcId = 0;
81 * A key listener that tracks the modifier keys and allows the full ASCII
82 * character set to be entered.
84 private TermKeyListener mKeyListener;
87 * The name of our emulator view in the view resource.
89 private static final int EMULATOR_VIEW = R.id.emulatorView;
91 private final static String DEFAULT_SHELL = "/system/bin/sh -";
93 private String mInitialCommand;
94 private final static String DEFAULT_INITIAL_COMMAND =
95 "export PATH=/data/local/bin:$PATH";
97 private SharedPreferences mPrefs;
98 private TermSettings mSettings;
100 private final static int SELECT_TEXT_ID = 0;
101 private final static int COPY_ALL_ID = 1;
102 private final static int PASTE_ID = 2;
104 private boolean mAlreadyStarted = false;
106 public TermService mTermService;
107 private Intent TSIntent;
109 private PowerManager.WakeLock mWakeLock;
110 private WifiManager.WifiLock mWifiLock;
113 public void onCreate(Bundle icicle) {
114 super.onCreate(icicle);
115 Log.e(TermDebug.LOG_TAG, "onCreate");
116 mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
117 mSettings = new TermSettings(mPrefs);
119 TSIntent = new Intent(this, TermService.class);
120 startService(TSIntent);
122 setContentView(R.layout.term_activity);
124 mEmulatorView = (EmulatorView) findViewById(EMULATOR_VIEW);
126 DisplayMetrics metrics = new DisplayMetrics();
127 getWindowManager().getDefaultDisplay().getMetrics(metrics);
128 mEmulatorView.setScaledDensity(metrics.scaledDensity);
132 mKeyListener = new TermKeyListener();
134 mEmulatorView.setFocusable(true);
135 mEmulatorView.setFocusableInTouchMode(true);
136 mEmulatorView.requestFocus();
137 mEmulatorView.register(this, mKeyListener);
139 registerForContextMenu(mEmulatorView);
141 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
142 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TermDebug.LOG_TAG);
143 WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE);
144 mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL, TermDebug.LOG_TAG);
147 mAlreadyStarted = true;
151 public void onDestroy() {
154 Exec.hangupProcessGroup(mProcId);
157 if (mTermFd != null) {
161 if (mWakeLock.isHeld()) {
164 if (mWifiLock.isHeld()) {
167 stopService(TSIntent);
170 private void startListening() {
171 int[] processId = new int[1];
173 createSubprocess(processId);
174 mProcId = processId[0];
176 final Handler handler = new Handler() {
178 public void handleMessage(Message msg) {
182 Runnable watchForDeath = new Runnable() {
185 Log.i(TermDebug.LOG_TAG, "waiting for: " + mProcId);
186 int result = Exec.waitFor(mProcId);
187 Log.i(TermDebug.LOG_TAG, "Subprocess exited: " + result);
188 handler.sendEmptyMessage(result);
192 Thread watcher = new Thread(watchForDeath);
195 mTermOut = new FileOutputStream(mTermFd);
197 mEmulatorView.initialize(mTermFd, mTermOut);
199 /* Check whether we've received an initial command from the
200 * launching application
202 mInitialCommand = mSettings.getInitialCommand();
203 String iInitialCommand = getIntent().getStringExtra("jackpal.androidterm.iInitialCommand");
204 if (iInitialCommand != null) {
205 if (mInitialCommand != null) {
206 mInitialCommand += "\r" + iInitialCommand;
208 mInitialCommand = iInitialCommand;
212 sendInitialCommand();
215 private void sendInitialCommand() {
216 String initialCommand = mInitialCommand;
217 if (initialCommand == null || initialCommand.equals("")) {
218 initialCommand = DEFAULT_INITIAL_COMMAND;
220 if (initialCommand.length() > 0) {
221 write(initialCommand + '\r');
225 private void restart() {
226 startActivity(getIntent());
230 private void write(String data) {
232 mTermOut.write(data.getBytes());
234 } catch (IOException e) {
236 // We don't really care if the receiver isn't listening.
237 // We just make a best effort to answer the query.
241 private void createSubprocess(int[] processId) {
242 String shell = mSettings.getShell();
243 if (shell == null || shell.equals("")) {
244 shell = DEFAULT_SHELL;
246 ArrayList<String> args = parse(shell);
247 String arg0 = args.get(0);
250 if (args.size() >= 2) {
253 if (args.size() >= 3) {
256 mTermFd = Exec.createSubprocess(arg0, arg1, arg2, processId);
259 private ArrayList<String> parse(String cmd) {
261 final int WHITESPACE = 1;
262 final int INQUOTE = 2;
263 int state = WHITESPACE;
264 ArrayList<String> result = new ArrayList<String>();
265 int cmdLen = cmd.length();
266 StringBuilder builder = new StringBuilder();
267 for (int i = 0; i < cmdLen; i++) {
268 char c = cmd.charAt(i);
269 if (state == PLAIN) {
270 if (Character.isWhitespace(c)) {
271 result.add(builder.toString());
272 builder.delete(0,builder.length());
274 } else if (c == '"') {
279 } else if (state == WHITESPACE) {
280 if (Character.isWhitespace(c)) {
282 } else if (c == '"') {
288 } else if (state == INQUOTE) {
290 if (i + 1 < cmdLen) {
292 builder.append(cmd.charAt(i));
294 } else if (c == '"') {
301 if (builder.length() > 0) {
302 result.add(builder.toString());
307 private void updatePrefs() {
308 DisplayMetrics metrics = new DisplayMetrics();
309 getWindowManager().getDefaultDisplay().getMetrics(metrics);
310 mEmulatorView.updatePrefs(mSettings, metrics);
312 Window win = getWindow();
313 WindowManager.LayoutParams params = win.getAttributes();
314 final int FULLSCREEN = WindowManager.LayoutParams.FLAG_FULLSCREEN;
315 int desiredFlag = mSettings.showStatusBar() ? 0 : FULLSCREEN;
316 if (desiredFlag != (params.flags & FULLSCREEN)) {
317 if (mAlreadyStarted) {
318 // Can't switch to/from fullscreen after
319 // starting the activity.
322 win.setFlags(desiredFlag, FULLSCREEN);
329 public void onResume() {
331 mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
332 mSettings.readPrefs(mPrefs);
334 mEmulatorView.onResume();
338 public void onPause() {
340 mEmulatorView.onPause();
344 public void onConfigurationChanged(Configuration newConfig) {
345 super.onConfigurationChanged(newConfig);
347 mEmulatorView.updateSize(true);
351 public boolean onCreateOptionsMenu(Menu menu) {
352 getMenuInflater().inflate(R.menu.main, menu);
357 public boolean onOptionsItemSelected(MenuItem item) {
358 int id = item.getItemId();
359 if (id == R.id.menu_preferences) {
361 } else if (id == R.id.menu_reset) {
363 } else if (id == R.id.menu_send_email) {
365 } else if (id == R.id.menu_special_keys) {
367 } else if (id == R.id.menu_toggle_soft_keyboard) {
368 doToggleSoftKeyboard();
369 } else if (id == R.id.menu_toggle_wakelock) {
371 } else if (id == R.id.menu_toggle_wifilock) {
374 return super.onOptionsItemSelected(item);
378 public boolean onPrepareOptionsMenu(Menu menu) {
379 MenuItem wakeLockItem = menu.findItem(R.id.menu_toggle_wakelock);
380 MenuItem wifiLockItem = menu.findItem(R.id.menu_toggle_wifilock);
381 if (mWakeLock.isHeld()) {
382 wakeLockItem.setTitle(R.string.disable_wakelock);
384 wakeLockItem.setTitle(R.string.enable_wakelock);
386 if (mWifiLock.isHeld()) {
387 wifiLockItem.setTitle(R.string.disable_wifilock);
389 wifiLockItem.setTitle(R.string.enable_wifilock);
391 return super.onPrepareOptionsMenu(menu);
395 public void onCreateContextMenu(ContextMenu menu, View v,
396 ContextMenuInfo menuInfo) {
397 super.onCreateContextMenu(menu, v, menuInfo);
398 menu.setHeaderTitle(R.string.edit_text);
399 menu.add(0, SELECT_TEXT_ID, 0, R.string.select_text);
400 menu.add(0, COPY_ALL_ID, 0, R.string.copy_all);
401 menu.add(0, PASTE_ID, 0, R.string.paste);
403 menu.getItem(PASTE_ID).setEnabled(false);
408 public boolean onContextItemSelected(MenuItem item) {
409 switch (item.getItemId()) {
411 mEmulatorView.toggleSelectingText();
420 return super.onContextItemSelected(item);
424 private boolean canPaste() {
425 ClipboardManager clip = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
426 if (clip.hasText()) {
432 private void doPreferences() {
433 startActivity(new Intent(this, TermPreferences.class));
436 private void doResetTerminal() {
440 private void doEmailTranscript() {
441 // Don't really want to supply an address, but
442 // currently it's required, otherwise we get an
444 String addr = "user@example.com";
446 new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"
449 intent.putExtra("body", mEmulatorView.getTranscriptText().trim());
450 startActivity(intent);
453 private void doCopyAll() {
454 ClipboardManager clip = (ClipboardManager)
455 getSystemService(Context.CLIPBOARD_SERVICE);
456 clip.setText(mEmulatorView.getTranscriptText().trim());
459 private void doPaste() {
460 ClipboardManager clip = (ClipboardManager)
461 getSystemService(Context.CLIPBOARD_SERVICE);
462 CharSequence paste = clip.getText();
465 utf8 = paste.toString().getBytes("UTF-8");
466 } catch (UnsupportedEncodingException e) {
467 Log.e(TermDebug.LOG_TAG, "UTF-8 encoding not found.");
471 mTermOut.write(utf8);
472 } catch (IOException e) {
473 Log.e(TermDebug.LOG_TAG, "could not write paste text to terminal.");
477 private void doDocumentKeys() {
478 AlertDialog.Builder dialog = new AlertDialog.Builder(this);
479 Resources r = getResources();
480 dialog.setTitle(r.getString(R.string.control_key_dialog_title));
482 formatMessage(mSettings.getControlKeyId(), TermSettings.CONTROL_KEY_ID_NONE,
483 r, R.array.control_keys_short_names,
484 R.string.control_key_dialog_control_text,
485 R.string.control_key_dialog_control_disabled_text, "CTRLKEY")
487 formatMessage(mSettings.getFnKeyId(), TermSettings.FN_KEY_ID_NONE,
488 r, R.array.fn_keys_short_names,
489 R.string.control_key_dialog_fn_text,
490 R.string.control_key_dialog_fn_disabled_text, "FNKEY"));
494 private String formatMessage(int keyId, int disabledKeyId,
495 Resources r, int arrayId,
497 int disabledId, String regex) {
498 if (keyId == disabledKeyId) {
499 return r.getString(disabledId);
501 String[] keyNames = r.getStringArray(arrayId);
502 String keyName = keyNames[keyId];
503 String template = r.getString(enabledId);
504 String result = template.replaceAll(regex, keyName);
508 private void doToggleSoftKeyboard() {
509 InputMethodManager imm = (InputMethodManager)
510 getSystemService(Context.INPUT_METHOD_SERVICE);
511 imm.toggleSoftInput(InputMethodManager.SHOW_FORCED,0);
515 private void doToggleWakeLock() {
516 if (mWakeLock.isHeld()) {
523 private void doToggleWifiLock() {
524 if (mWifiLock.isHeld()) {