OSDN Git Service

Add configurable back button behavior
[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.UnsupportedEncodingException;
20 import java.util.ArrayList;
21
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;
53
54 import jackpal.androidterm.session.TermSession;
55 import jackpal.androidterm.util.TermSettings;
56
57 /**
58  * A terminal emulator activity.
59  */
60
61 public class Term extends Activity {
62     /**
63      * The ViewFlipper which holds the collection of EmulatorView widgets.
64      */
65     private TermViewFlipper mViewFlipper;
66
67     /**
68      * The name of the ViewFlipper in the resources.
69      */
70     private static final int VIEW_FLIPPER = R.id.view_flipper;
71
72     private ArrayList<TermSession> mTermSessions;
73
74     private SharedPreferences mPrefs;
75     private TermSettings mSettings;
76
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;
80
81     private boolean mAlreadyStarted = false;
82     private boolean mStopServiceOnFinish = false;
83
84     private Intent TSIntent;
85
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;
89
90     private PowerManager.WakeLock mWakeLock;
91     private WifiManager.WifiLock mWifiLock;
92
93     private boolean mBackKeyPressed;
94
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();
102         }
103
104         public void onServiceDisconnected(ComponentName arg0) {
105             mTermService = null;
106         }
107     };
108
109     @Override
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);
115
116         TSIntent = new Intent(this, TermService.class);
117         startService(TSIntent);
118
119         if (!bindService(TSIntent, mTSConnection, BIND_AUTO_CREATE)) {
120             Log.w(TermDebug.LOG_TAG, "bind to service failed!");
121         }
122
123         setContentView(R.layout.term_activity);
124         mViewFlipper = (TermViewFlipper) findViewById(VIEW_FLIPPER);
125
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);
130
131         updatePrefs();
132         mAlreadyStarted = true;
133     }
134
135     private void populateViewFlipper() {
136         if (mTermService != null) {
137             mTermSessions = mTermService.getSessions();
138
139             if (mTermSessions.size() == 0) {
140                 mTermSessions.add(createTermSession());
141             }
142
143             for (TermSession session : mTermSessions) {
144                 EmulatorView view = createEmulatorView(session);
145                 mViewFlipper.addView(view);
146             }
147
148             updatePrefs();
149         }
150     }
151
152     @Override
153     public void onDestroy() {
154         super.onDestroy();
155         mViewFlipper.removeAllViews();
156         unbindService(mTSConnection);
157         if (mStopServiceOnFinish) {
158             stopService(TSIntent);
159         }
160         mTermService = null;
161         mTSConnection = null;
162         if (mWakeLock.isHeld()) {
163             mWakeLock.release();
164         }
165         if (mWifiLock.isHeld()) {
166             mWifiLock.release();
167         }
168     }
169
170     private void restart() {
171         startActivity(getIntent());
172         finish();
173     }
174
175     private TermSession createTermSession() {
176         /* Check whether we've received an initial command from the
177          * launching application
178          */
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;
184             } else {
185                 initialCommand = iInitialCommand;
186             }
187         }
188
189         return new TermSession(mSettings, null, initialCommand);
190     }
191
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);
196
197         FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
198             FrameLayout.LayoutParams.WRAP_CONTENT,
199             FrameLayout.LayoutParams.WRAP_CONTENT,
200             Gravity.LEFT
201         );
202         emulatorView.setLayoutParams(params);
203
204         session.setUpdateCallback(emulatorView.getUpdateCallback());
205
206         registerForContextMenu(emulatorView);
207
208         return emulatorView;
209     }
210
211     private TermSession getCurrentTermSession() {
212         return mTermSessions.get(mViewFlipper.getDisplayedChild());
213     }
214
215     private EmulatorView getCurrentEmulatorView() {
216         return (EmulatorView) mViewFlipper.getCurrentView();
217     }
218
219     private void updatePrefs() {
220         DisplayMetrics metrics = new DisplayMetrics();
221         getWindowManager().getDefaultDisplay().getMetrics(metrics);
222
223         for (View v : mViewFlipper) {
224             ((EmulatorView) v).setDensity(metrics);
225             ((EmulatorView) v).updatePrefs(mSettings);
226         }
227         if (mTermSessions != null) {
228             for (TermSession session : mTermSessions) {
229                 session.updatePrefs(mSettings);
230             }
231         }
232         {
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.
241                     restart();
242                 } else {
243                     win.setFlags(desiredFlag, FULLSCREEN);
244                 }
245             }
246         }
247     }
248
249     @Override
250     public void onResume() {
251         super.onResume();
252
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())) {
257                     v.onPause();
258                     mViewFlipper.removeView(v);
259                     --i;
260                 }
261             }
262         }
263
264         mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
265         mSettings.readPrefs(mPrefs);
266         updatePrefs();
267
268         if (onResumeSelectWindow >= 0) {
269             mViewFlipper.setDisplayedChild(onResumeSelectWindow);
270             onResumeSelectWindow = -1;
271         } else {
272             mViewFlipper.resumeCurrentView();
273         }
274     }
275
276     @Override
277     public void onPause() {
278         super.onPause();
279
280         mViewFlipper.pauseCurrentView();
281
282         /* Explicitly close the input method
283            Otherwise, the soft keyboard could cover up whatever activity takes
284            our place */
285         final IBinder token = mViewFlipper.getWindowToken();
286         new Thread() {
287             @Override
288             public void run() {
289                 InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
290                 imm.hideSoftInputFromWindow(token, 0);
291             }
292         }.start();
293     }
294
295     @Override
296     public void onConfigurationChanged(Configuration newConfig) {
297         super.onConfigurationChanged(newConfig);
298
299         EmulatorView v = (EmulatorView) mViewFlipper.getCurrentView();
300         if (v != null) {
301             v.updateSize(true);
302         }
303     }
304
305     @Override
306     public boolean onCreateOptionsMenu(Menu menu) {
307         getMenuInflater().inflate(R.menu.main, menu);
308         return true;
309     }
310
311     @Override
312     public boolean onOptionsItemSelected(MenuItem item) {
313         int id = item.getItemId();
314         if (id == R.id.menu_preferences) {
315             doPreferences();
316         } else if (id == R.id.menu_new_window) {
317             doCreateNewWindow();
318         } else if (id == R.id.menu_close_window) {
319             doCloseWindow();
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) {
323             doResetTerminal();
324         } else if (id == R.id.menu_send_email) {
325             doEmailTranscript();
326         } else if (id == R.id.menu_special_keys) {
327             doDocumentKeys();
328         } else if (id == R.id.menu_toggle_soft_keyboard) {
329             doToggleSoftKeyboard();
330         } else if (id == R.id.menu_toggle_wakelock) {
331             doToggleWakeLock();
332         } else if (id == R.id.menu_toggle_wifilock) {
333             doToggleWifiLock();
334         }
335         return super.onOptionsItemSelected(item);
336     }
337
338     private void doCreateNewWindow() {
339         if (mTermSessions == null) {
340             Log.w(TermDebug.LOG_TAG, "Couldn't create new window because mTermSessions == null");
341             return;
342         }
343
344         TermSession session = createTermSession();
345         mTermSessions.add(session);
346
347         EmulatorView view = createEmulatorView(session);
348         view.updatePrefs(mSettings);
349
350         mViewFlipper.addView(view);
351         mViewFlipper.setDisplayedChild(mViewFlipper.getChildCount()-1);
352     }
353
354     private void doCloseWindow() {
355         if (mTermSessions == null) {
356             return;
357         }
358
359         EmulatorView view = getCurrentEmulatorView();
360         if (view == null) {
361             return;
362         }
363         TermSession session = mTermSessions.remove(mViewFlipper.getDisplayedChild());
364         view.onPause();
365         session.finish();
366         mViewFlipper.removeView(view);
367         if (mTermSessions.size() == 0) {
368             mStopServiceOnFinish = true;
369             finish();
370         } else {
371             mViewFlipper.showNext();
372         }
373     }
374
375     @Override
376     protected void onActivityResult(int request, int result, Intent data) {
377         switch (request) {
378         case REQUEST_CHOOSE_WINDOW:
379             if (result == RESULT_OK && data != null) {
380                 int position = data.getIntExtra(EXTRA_WINDOW_ID, -2);
381                 if (position >= 0) {
382                     // Switch windows after session list is in sync, not here
383                     onResumeSelectWindow = position;
384                 } else if (position == -1) {
385                     doCreateNewWindow();
386                 }
387             } else {
388                 // Close the activity if user closed all sessions
389                 if (mTermSessions.size() == 0) {
390                     mStopServiceOnFinish = true;
391                     finish();
392                 }
393             }
394             break;
395         }
396     }
397
398     @Override
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);
404         } else {
405             wakeLockItem.setTitle(R.string.enable_wakelock);
406         }
407         if (mWifiLock.isHeld()) {
408             wifiLockItem.setTitle(R.string.disable_wifilock);
409         } else {
410             wifiLockItem.setTitle(R.string.enable_wifilock);
411         }
412         return super.onPrepareOptionsMenu(menu);
413     }
414
415     @Override
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);
423       if (!canPaste()) {
424           menu.getItem(PASTE_ID).setEnabled(false);
425       }
426     }
427
428     @Override
429     public boolean onContextItemSelected(MenuItem item) {
430           switch (item.getItemId()) {
431           case SELECT_TEXT_ID:
432             getCurrentEmulatorView().toggleSelectingText();
433             return true;
434           case COPY_ALL_ID:
435             doCopyAll();
436             return true;
437           case PASTE_ID:
438             doPaste();
439             return true;
440           default:
441             return super.onContextItemSelected(item);
442           }
443         }
444
445     @Override
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
449            ignore it here */
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;
456             return true;
457         } else {
458             return super.onKeyDown(keyCode, event);
459         }
460     }
461
462     @Override
463     public boolean onKeyUp(int keyCode, KeyEvent event) {
464         switch (keyCode) {
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 */
469                 return false;
470             }
471             mBackKeyPressed = false;
472             switch (mSettings.getBackKeyAction()) {
473             case TermSettings.BACK_KEY_STOPS_SERVICE:
474                 mStopServiceOnFinish = true;
475             case TermSettings.BACK_KEY_CLOSES_ACTIVITY:
476                 finish();
477                 return true;
478             case TermSettings.BACK_KEY_CLOSES_WINDOW:
479                 doCloseWindow();
480                 return true;
481             default:
482                 return false;
483             }
484         default:
485             return super.onKeyUp(keyCode, event);
486         }
487     }
488
489     private boolean canPaste() {
490         ClipboardManager clip = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
491         if (clip.hasText()) {
492             return true;
493         }
494         return false;
495     }
496
497     private void doPreferences() {
498         startActivity(new Intent(this, TermPreferences.class));
499     }
500
501     private void doResetTerminal() {
502         TermSession session = getCurrentTermSession();
503         if (session != null) {
504             session.reset();
505         }
506     }
507
508     private void doEmailTranscript() {
509         // Don't really want to supply an address, but
510         // currently it's required, otherwise we get an
511         // exception.
512         String addr = "user@example.com";
513         Intent intent =
514                 new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"
515                         + addr));
516
517         intent.putExtra("body", getCurrentTermSession().getTranscriptText().trim());
518         startActivity(intent);
519     }
520
521     private void doCopyAll() {
522         ClipboardManager clip = (ClipboardManager)
523              getSystemService(Context.CLIPBOARD_SERVICE);
524         clip.setText(getCurrentTermSession().getTranscriptText().trim());
525     }
526
527     private void doPaste() {
528         ClipboardManager clip = (ClipboardManager)
529          getSystemService(Context.CLIPBOARD_SERVICE);
530         CharSequence paste = clip.getText();
531         byte[] utf8;
532         try {
533             utf8 = paste.toString().getBytes("UTF-8");
534         } catch (UnsupportedEncodingException e) {
535             Log.e(TermDebug.LOG_TAG, "UTF-8 encoding not found.");
536             return;
537         }
538         getCurrentTermSession().write(paste.toString());
539     }
540
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));
545         dialog.setMessage(
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")
550             + "\n\n" +
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"));
555          dialog.show();
556      }
557
558      private String formatMessage(int keyId, int disabledKeyId,
559          Resources r, int arrayId,
560          int enabledId,
561          int disabledId, String regex) {
562          if (keyId == disabledKeyId) {
563              return r.getString(disabledId);
564          }
565          String[] keyNames = r.getStringArray(arrayId);
566          String keyName = keyNames[keyId];
567          String template = r.getString(enabledId);
568          String result = template.replaceAll(regex, keyName);
569          return result;
570     }
571
572     private void doToggleSoftKeyboard() {
573         InputMethodManager imm = (InputMethodManager)
574             getSystemService(Context.INPUT_METHOD_SERVICE);
575         imm.toggleSoftInput(InputMethodManager.SHOW_FORCED,0);
576
577     }
578
579     private void doToggleWakeLock() {
580         if (mWakeLock.isHeld()) {
581             mWakeLock.release();
582         } else {
583             mWakeLock.acquire();
584         }
585     }
586
587     private void doToggleWifiLock() {
588         if (mWifiLock.isHeld()) {
589             mWifiLock.release();
590         } else {
591             mWifiLock.acquire();
592         }
593     }
594 }