OSDN Git Service

Make changes to UTF-8 preference take effect immediately in running sessions
[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.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;
52
53 import jackpal.androidterm.session.TermSession;
54 import jackpal.androidterm.util.TermSettings;
55
56 /**
57  * A terminal emulator activity.
58  */
59
60 public class Term extends Activity {
61     /**
62      * The ViewFlipper which holds the collection of EmulatorView widgets.
63      */
64     private TermViewFlipper mViewFlipper;
65
66     /**
67      * The name of the ViewFlipper in the resources.
68      */
69     private static final int VIEW_FLIPPER = R.id.view_flipper;
70
71     private ArrayList<TermSession> mTermSessions;
72
73     private SharedPreferences mPrefs;
74     private TermSettings mSettings;
75
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;
79
80     private boolean mAlreadyStarted = false;
81
82     private Intent TSIntent;
83
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;
87
88     private PowerManager.WakeLock mWakeLock;
89     private WifiManager.WifiLock mWifiLock;
90
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();
98         }
99
100         public void onServiceDisconnected(ComponentName arg0) {
101             mTermService = null;
102         }
103     };
104
105     @Override
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);
111
112         TSIntent = new Intent(this, TermService.class);
113         startService(TSIntent);
114
115         if (!bindService(TSIntent, mTSConnection, BIND_AUTO_CREATE)) {
116             Log.w(TermDebug.LOG_TAG, "bind to service failed!");
117         }
118
119         setContentView(R.layout.term_activity);
120         mViewFlipper = (TermViewFlipper) findViewById(VIEW_FLIPPER);
121
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);
126
127         updatePrefs();
128         mAlreadyStarted = true;
129     }
130
131     private void populateViewFlipper() {
132         if (mTermService != null) {
133             mTermSessions = mTermService.getSessions();
134
135             if (mTermSessions.size() == 0) {
136                 mTermSessions.add(createTermSession());
137             }
138
139             for (TermSession session : mTermSessions) {
140                 EmulatorView view = createEmulatorView(session);
141                 mViewFlipper.addView(view);
142             }
143
144             updatePrefs();
145         }
146     }
147
148     @Override
149     public void onDestroy() {
150         super.onDestroy();
151         mViewFlipper.removeAllViews();
152         unbindService(mTSConnection);
153         stopService(TSIntent);
154         mTermService = null;
155         mTSConnection = null;
156         if (mWakeLock.isHeld()) {
157             mWakeLock.release();
158         }
159         if (mWifiLock.isHeld()) {
160             mWifiLock.release();
161         }
162     }
163
164     private void restart() {
165         startActivity(getIntent());
166         finish();
167     }
168
169     private TermSession createTermSession() {
170         /* Check whether we've received an initial command from the
171          * launching application
172          */
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;
178             } else {
179                 initialCommand = iInitialCommand;
180             }
181         }
182
183         return new TermSession(mSettings, null, initialCommand);
184     }
185
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);
190
191         FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
192             FrameLayout.LayoutParams.WRAP_CONTENT,
193             FrameLayout.LayoutParams.WRAP_CONTENT,
194             Gravity.LEFT
195         );
196         emulatorView.setLayoutParams(params);
197
198         session.setUpdateCallback(emulatorView.getUpdateCallback());
199
200         registerForContextMenu(emulatorView);
201
202         return emulatorView;
203     }
204
205     private TermSession getCurrentTermSession() {
206         return mTermSessions.get(mViewFlipper.getDisplayedChild());
207     }
208
209     private EmulatorView getCurrentEmulatorView() {
210         return (EmulatorView) mViewFlipper.getCurrentView();
211     }
212
213     private void updatePrefs() {
214         DisplayMetrics metrics = new DisplayMetrics();
215         getWindowManager().getDefaultDisplay().getMetrics(metrics);
216
217         for (View v : mViewFlipper) {
218             ((EmulatorView) v).setDensity(metrics);
219             ((EmulatorView) v).updatePrefs(mSettings);
220         }
221         if (mTermSessions != null) {
222             for (TermSession session : mTermSessions) {
223                 session.updatePrefs(mSettings);
224             }
225         }
226         {
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.
235                     restart();
236                 } else {
237                     win.setFlags(desiredFlag, FULLSCREEN);
238                 }
239             }
240         }
241     }
242
243     @Override
244     public void onResume() {
245         super.onResume();
246
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())) {
251                     v.onPause();
252                     mViewFlipper.removeView(v);
253                     --i;
254                 }
255             }
256         }
257
258         mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
259         mSettings.readPrefs(mPrefs);
260         updatePrefs();
261
262         if (onResumeSelectWindow >= 0) {
263             mViewFlipper.setDisplayedChild(onResumeSelectWindow);
264             onResumeSelectWindow = -1;
265         } else {
266             mViewFlipper.resumeCurrentView();
267         }
268     }
269
270     @Override
271     public void onPause() {
272         super.onPause();
273
274         mViewFlipper.pauseCurrentView();
275
276         /* Explicitly close the input method
277            Otherwise, the soft keyboard could cover up whatever activity takes
278            our place */
279         final IBinder token = mViewFlipper.getWindowToken();
280         new Thread() {
281             @Override
282             public void run() {
283                 InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
284                 imm.hideSoftInputFromWindow(token, 0);
285             }
286         }.start();
287     }
288
289     @Override
290     public void onConfigurationChanged(Configuration newConfig) {
291         super.onConfigurationChanged(newConfig);
292
293         EmulatorView v = (EmulatorView) mViewFlipper.getCurrentView();
294         if (v != null) {
295             v.updateSize(true);
296         }
297     }
298
299     @Override
300     public boolean onCreateOptionsMenu(Menu menu) {
301         getMenuInflater().inflate(R.menu.main, menu);
302         return true;
303     }
304
305     @Override
306     public boolean onOptionsItemSelected(MenuItem item) {
307         int id = item.getItemId();
308         if (id == R.id.menu_preferences) {
309             doPreferences();
310         } else if (id == R.id.menu_new_window) {
311             doCreateNewWindow();
312         } else if (id == R.id.menu_close_window) {
313             doCloseWindow();
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) {
317             doResetTerminal();
318         } else if (id == R.id.menu_send_email) {
319             doEmailTranscript();
320         } else if (id == R.id.menu_special_keys) {
321             doDocumentKeys();
322         } else if (id == R.id.menu_toggle_soft_keyboard) {
323             doToggleSoftKeyboard();
324         } else if (id == R.id.menu_toggle_wakelock) {
325             doToggleWakeLock();
326         } else if (id == R.id.menu_toggle_wifilock) {
327             doToggleWifiLock();
328         }
329         return super.onOptionsItemSelected(item);
330     }
331
332     private void doCreateNewWindow() {
333         if (mTermSessions == null) {
334             Log.w(TermDebug.LOG_TAG, "Couldn't create new window because mTermSessions == null");
335             return;
336         }
337
338         TermSession session = createTermSession();
339         mTermSessions.add(session);
340
341         EmulatorView view = createEmulatorView(session);
342         view.updatePrefs(mSettings);
343
344         mViewFlipper.addView(view);
345         mViewFlipper.setDisplayedChild(mViewFlipper.getChildCount()-1);
346     }
347
348     private void doCloseWindow() {
349         if (mTermSessions == null) {
350             return;
351         }
352
353         EmulatorView view = getCurrentEmulatorView();
354         if (view == null) {
355             return;
356         }
357         TermSession session = mTermSessions.remove(mViewFlipper.getDisplayedChild());
358         view.onPause();
359         session.finish();
360         mViewFlipper.removeView(view);
361         if (mTermSessions.size() == 0) {
362             finish();
363         } else {
364             mViewFlipper.showNext();
365         }
366     }
367
368     @Override
369     protected void onActivityResult(int request, int result, Intent data) {
370         switch (request) {
371         case REQUEST_CHOOSE_WINDOW:
372             if (result == RESULT_OK && data != null) {
373                 int position = data.getIntExtra(EXTRA_WINDOW_ID, -2);
374                 if (position >= 0) {
375                     // Switch windows after session list is in sync, not here
376                     onResumeSelectWindow = position;
377                 } else if (position == -1) {
378                     doCreateNewWindow();
379                 }
380             } else {
381                 // Close the activity if user closed all sessions
382                 if (mTermSessions.size() == 0) {
383                     finish();
384                 }
385             }
386             break;
387         }
388     }
389
390     @Override
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);
396         } else {
397             wakeLockItem.setTitle(R.string.enable_wakelock);
398         }
399         if (mWifiLock.isHeld()) {
400             wifiLockItem.setTitle(R.string.disable_wifilock);
401         } else {
402             wifiLockItem.setTitle(R.string.enable_wifilock);
403         }
404         return super.onPrepareOptionsMenu(menu);
405     }
406
407     @Override
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);
415       if (!canPaste()) {
416           menu.getItem(PASTE_ID).setEnabled(false);
417       }
418     }
419
420     @Override
421     public boolean onContextItemSelected(MenuItem item) {
422           switch (item.getItemId()) {
423           case SELECT_TEXT_ID:
424             getCurrentEmulatorView().toggleSelectingText();
425             return true;
426           case COPY_ALL_ID:
427             doCopyAll();
428             return true;
429           case PASTE_ID:
430             doPaste();
431             return true;
432           default:
433             return super.onContextItemSelected(item);
434           }
435         }
436
437     private boolean canPaste() {
438         ClipboardManager clip = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
439         if (clip.hasText()) {
440             return true;
441         }
442         return false;
443     }
444
445     private void doPreferences() {
446         startActivity(new Intent(this, TermPreferences.class));
447     }
448
449     private void doResetTerminal() {
450         restart();
451     }
452
453     private void doEmailTranscript() {
454         // Don't really want to supply an address, but
455         // currently it's required, otherwise we get an
456         // exception.
457         String addr = "user@example.com";
458         Intent intent =
459                 new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"
460                         + addr));
461
462         intent.putExtra("body", getCurrentTermSession().getTranscriptText().trim());
463         startActivity(intent);
464     }
465
466     private void doCopyAll() {
467         ClipboardManager clip = (ClipboardManager)
468              getSystemService(Context.CLIPBOARD_SERVICE);
469         clip.setText(getCurrentTermSession().getTranscriptText().trim());
470     }
471
472     private void doPaste() {
473         ClipboardManager clip = (ClipboardManager)
474          getSystemService(Context.CLIPBOARD_SERVICE);
475         CharSequence paste = clip.getText();
476         byte[] utf8;
477         try {
478             utf8 = paste.toString().getBytes("UTF-8");
479         } catch (UnsupportedEncodingException e) {
480             Log.e(TermDebug.LOG_TAG, "UTF-8 encoding not found.");
481             return;
482         }
483         getCurrentTermSession().write(paste.toString());
484     }
485
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));
490         dialog.setMessage(
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")
495             + "\n\n" +
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"));
500          dialog.show();
501      }
502
503      private String formatMessage(int keyId, int disabledKeyId,
504          Resources r, int arrayId,
505          int enabledId,
506          int disabledId, String regex) {
507          if (keyId == disabledKeyId) {
508              return r.getString(disabledId);
509          }
510          String[] keyNames = r.getStringArray(arrayId);
511          String keyName = keyNames[keyId];
512          String template = r.getString(enabledId);
513          String result = template.replaceAll(regex, keyName);
514          return result;
515     }
516
517     private void doToggleSoftKeyboard() {
518         InputMethodManager imm = (InputMethodManager)
519             getSystemService(Context.INPUT_METHOD_SERVICE);
520         imm.toggleSoftInput(InputMethodManager.SHOW_FORCED,0);
521
522     }
523
524     private void doToggleWakeLock() {
525         if (mWakeLock.isHeld()) {
526             mWakeLock.release();
527         } else {
528             mWakeLock.acquire();
529         }
530     }
531
532     private void doToggleWifiLock() {
533         if (mWifiLock.isHeld()) {
534             mWifiLock.release();
535         } else {
536             mWifiLock.acquire();
537         }
538     }
539 }