OSDN Git Service

Modify the activity and view to use the new TermSettings class.
[android-x86/packages-apps-AndroidTerm.git] / src / jackpal / androidterm / Term.java
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package jackpal.androidterm;
18
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;
24
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;
51
52 import jackpal.androidterm.util.TermSettings;
53
54 /**
55  * A terminal emulator activity.
56  */
57
58 public class Term extends Activity {
59     /**
60      * Our main view. Displays the emulated terminal screen.
61      */
62     private EmulatorView mEmulatorView;
63
64     /**
65      * The pseudo-teletype (pty) file descriptor that we use to communicate with
66      * another process, typically a shell.
67      */
68     private FileDescriptor mTermFd;
69
70     /**
71      * Used to send data to the remote process.
72      */
73     private FileOutputStream mTermOut;
74
75     /**
76      * The process ID of the remote process.
77      */
78     private int mProcId = 0;
79
80     /**
81      * A key listener that tracks the modifier keys and allows the full ASCII
82      * character set to be entered.
83      */
84     private TermKeyListener mKeyListener;
85
86     /**
87      * The name of our emulator view in the view resource.
88      */
89     private static final int EMULATOR_VIEW = R.id.emulatorView;
90
91     private final static String DEFAULT_SHELL = "/system/bin/sh -";
92
93     private String mInitialCommand;
94     private final static String DEFAULT_INITIAL_COMMAND =
95         "export PATH=/data/local/bin:$PATH";
96
97     private SharedPreferences mPrefs;
98     private TermSettings mSettings;
99
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;
103
104     private boolean mAlreadyStarted = false;
105
106     public TermService mTermService;
107     private Intent TSIntent;
108
109     private PowerManager.WakeLock mWakeLock;
110     private WifiManager.WifiLock mWifiLock;
111
112     @Override
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);
118
119         TSIntent = new Intent(this, TermService.class);
120         startService(TSIntent);
121
122         setContentView(R.layout.term_activity);
123
124         mEmulatorView = (EmulatorView) findViewById(EMULATOR_VIEW);
125
126         DisplayMetrics metrics = new DisplayMetrics();
127         getWindowManager().getDefaultDisplay().getMetrics(metrics);
128         mEmulatorView.setScaledDensity(metrics.scaledDensity);
129
130         startListening();
131
132         mKeyListener = new TermKeyListener();
133
134         mEmulatorView.setFocusable(true);
135         mEmulatorView.setFocusableInTouchMode(true);
136         mEmulatorView.requestFocus();
137         mEmulatorView.register(this, mKeyListener);
138
139         registerForContextMenu(mEmulatorView);
140
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);
145
146         updatePrefs();
147         mAlreadyStarted = true;
148     }
149
150     @Override
151     public void onDestroy() {
152         super.onDestroy();
153         if (mProcId != 0) {
154             Exec.hangupProcessGroup(mProcId);
155             mProcId = 0;
156         }
157         if (mTermFd != null) {
158             Exec.close(mTermFd);
159             mTermFd = null;
160         }
161         if (mWakeLock.isHeld()) {
162             mWakeLock.release();
163         }
164         if (mWifiLock.isHeld()) {
165             mWifiLock.release();
166         }
167         stopService(TSIntent);
168     }
169
170     private void startListening() {
171         int[] processId = new int[1];
172
173         createSubprocess(processId);
174         mProcId = processId[0];
175
176         final Handler handler = new Handler() {
177             @Override
178             public void handleMessage(Message msg) {
179             }
180         };
181
182         Runnable watchForDeath = new Runnable() {
183
184             public void run() {
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);
189              }
190
191         };
192         Thread watcher = new Thread(watchForDeath);
193         watcher.start();
194
195         mTermOut = new FileOutputStream(mTermFd);
196
197         mEmulatorView.initialize(mTermFd, mTermOut);
198
199         /* Check whether we've received an initial command from the
200          * launching application
201          */
202         mInitialCommand = mSettings.getInitialCommand();
203         String iInitialCommand = getIntent().getStringExtra("jackpal.androidterm.iInitialCommand");
204         if (iInitialCommand != null) {
205             if (mInitialCommand != null) {
206                 mInitialCommand += "\r" + iInitialCommand;
207             } else {
208                 mInitialCommand = iInitialCommand;
209             }
210         }
211
212         sendInitialCommand();
213     }
214
215     private void sendInitialCommand() {
216         String initialCommand = mInitialCommand;
217         if (initialCommand == null || initialCommand.equals("")) {
218             initialCommand = DEFAULT_INITIAL_COMMAND;
219         }
220         if (initialCommand.length() > 0) {
221             write(initialCommand + '\r');
222         }
223     }
224
225     private void restart() {
226         startActivity(getIntent());
227         finish();
228     }
229
230     private void write(String data) {
231         try {
232             mTermOut.write(data.getBytes());
233             mTermOut.flush();
234         } catch (IOException e) {
235             // Ignore exception
236             // We don't really care if the receiver isn't listening.
237             // We just make a best effort to answer the query.
238         }
239     }
240
241     private void createSubprocess(int[] processId) {
242         String shell = mSettings.getShell();
243         if (shell == null || shell.equals("")) {
244             shell = DEFAULT_SHELL;
245         }
246         ArrayList<String> args = parse(shell);
247         String arg0 = args.get(0);
248         String arg1 = null;
249         String arg2 = null;
250         if (args.size() >= 2) {
251             arg1 = args.get(1);
252         }
253         if (args.size() >= 3) {
254             arg2 = args.get(2);
255         }
256         mTermFd = Exec.createSubprocess(arg0, arg1, arg2, processId);
257     }
258
259     private ArrayList<String> parse(String cmd) {
260         final int PLAIN = 0;
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());
273                     state = WHITESPACE;
274                 } else if (c == '"') {
275                     state = INQUOTE;
276                 } else {
277                     builder.append(c);
278                 }
279             } else if (state == WHITESPACE) {
280                 if (Character.isWhitespace(c)) {
281                     // do nothing
282                 } else if (c == '"') {
283                     state = INQUOTE;
284                 } else {
285                     state = PLAIN;
286                     builder.append(c);
287                 }
288             } else if (state == INQUOTE) {
289                 if (c == '\\') {
290                     if (i + 1 < cmdLen) {
291                         i += 1;
292                         builder.append(cmd.charAt(i));
293                     }
294                 } else if (c == '"') {
295                     state = PLAIN;
296                 } else {
297                     builder.append(c);
298                 }
299             }
300         }
301         if (builder.length() > 0) {
302             result.add(builder.toString());
303         }
304         return result;
305     }
306
307     private void updatePrefs() {
308         DisplayMetrics metrics = new DisplayMetrics();
309         getWindowManager().getDefaultDisplay().getMetrics(metrics);
310         mEmulatorView.updatePrefs(mSettings, metrics);
311         {
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.
320                     restart();
321                 } else {
322                     win.setFlags(desiredFlag, FULLSCREEN);
323                 }
324             }
325         }
326     }
327
328     @Override
329     public void onResume() {
330         super.onResume();
331         mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
332         mSettings.readPrefs(mPrefs);
333         updatePrefs();
334         mEmulatorView.onResume();
335     }
336
337     @Override
338     public void onPause() {
339         super.onPause();
340         mEmulatorView.onPause();
341     }
342
343     @Override
344     public void onConfigurationChanged(Configuration newConfig) {
345         super.onConfigurationChanged(newConfig);
346
347         mEmulatorView.updateSize(true);
348     }
349
350     @Override
351     public boolean onCreateOptionsMenu(Menu menu) {
352         getMenuInflater().inflate(R.menu.main, menu);
353         return true;
354     }
355
356     @Override
357     public boolean onOptionsItemSelected(MenuItem item) {
358         int id = item.getItemId();
359         if (id == R.id.menu_preferences) {
360             doPreferences();
361         } else if (id == R.id.menu_reset) {
362             doResetTerminal();
363         } else if (id == R.id.menu_send_email) {
364             doEmailTranscript();
365         } else if (id == R.id.menu_special_keys) {
366             doDocumentKeys();
367         } else if (id == R.id.menu_toggle_soft_keyboard) {
368             doToggleSoftKeyboard();
369         } else if (id == R.id.menu_toggle_wakelock) {
370             doToggleWakeLock();
371         } else if (id == R.id.menu_toggle_wifilock) {
372             doToggleWifiLock();
373         }
374         return super.onOptionsItemSelected(item);
375     }
376
377     @Override
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);
383         } else {
384             wakeLockItem.setTitle(R.string.enable_wakelock);
385         }
386         if (mWifiLock.isHeld()) {
387             wifiLockItem.setTitle(R.string.disable_wifilock);
388         } else {
389             wifiLockItem.setTitle(R.string.enable_wifilock);
390         }
391         return super.onPrepareOptionsMenu(menu);
392     }
393
394     @Override
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);
402       if (!canPaste()) {
403           menu.getItem(PASTE_ID).setEnabled(false);
404       }
405     }
406
407     @Override
408     public boolean onContextItemSelected(MenuItem item) {
409           switch (item.getItemId()) {
410           case SELECT_TEXT_ID:
411             mEmulatorView.toggleSelectingText();
412             return true;
413           case COPY_ALL_ID:
414             doCopyAll();
415             return true;
416           case PASTE_ID:
417             doPaste();
418             return true;
419           default:
420             return super.onContextItemSelected(item);
421           }
422         }
423
424     private boolean canPaste() {
425         ClipboardManager clip = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
426         if (clip.hasText()) {
427             return true;
428         }
429         return false;
430     }
431
432     private void doPreferences() {
433         startActivity(new Intent(this, TermPreferences.class));
434     }
435
436     private void doResetTerminal() {
437         restart();
438     }
439
440     private void doEmailTranscript() {
441         // Don't really want to supply an address, but
442         // currently it's required, otherwise we get an
443         // exception.
444         String addr = "user@example.com";
445         Intent intent =
446                 new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"
447                         + addr));
448
449         intent.putExtra("body", mEmulatorView.getTranscriptText().trim());
450         startActivity(intent);
451     }
452
453     private void doCopyAll() {
454         ClipboardManager clip = (ClipboardManager)
455              getSystemService(Context.CLIPBOARD_SERVICE);
456         clip.setText(mEmulatorView.getTranscriptText().trim());
457     }
458
459     private void doPaste() {
460         ClipboardManager clip = (ClipboardManager)
461          getSystemService(Context.CLIPBOARD_SERVICE);
462         CharSequence paste = clip.getText();
463         byte[] utf8;
464         try {
465             utf8 = paste.toString().getBytes("UTF-8");
466         } catch (UnsupportedEncodingException e) {
467             Log.e(TermDebug.LOG_TAG, "UTF-8 encoding not found.");
468             return;
469         }
470         try {
471             mTermOut.write(utf8);
472         } catch (IOException e) {
473             Log.e(TermDebug.LOG_TAG, "could not write paste text to terminal.");
474         }
475     }
476
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));
481         dialog.setMessage(
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")
486             + "\n\n" +
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"));
491          dialog.show();
492      }
493
494      private String formatMessage(int keyId, int disabledKeyId,
495          Resources r, int arrayId,
496          int enabledId,
497          int disabledId, String regex) {
498          if (keyId == disabledKeyId) {
499              return r.getString(disabledId);
500          }
501          String[] keyNames = r.getStringArray(arrayId);
502          String keyName = keyNames[keyId];
503          String template = r.getString(enabledId);
504          String result = template.replaceAll(regex, keyName);
505          return result;
506     }
507
508     private void doToggleSoftKeyboard() {
509         InputMethodManager imm = (InputMethodManager)
510             getSystemService(Context.INPUT_METHOD_SERVICE);
511         imm.toggleSoftInput(InputMethodManager.SHOW_FORCED,0);
512
513     }
514
515     private void doToggleWakeLock() {
516         if (mWakeLock.isHeld()) {
517             mWakeLock.release();
518         } else {
519             mWakeLock.acquire();
520         }
521     }
522
523     private void doToggleWifiLock() {
524         if (mWifiLock.isHeld()) {
525             mWifiLock.release();
526         } else {
527             mWifiLock.acquire();
528         }
529     }
530 }