/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.adblog.term; import jackpal.androidterm.emulatorview.ColorScheme; import jackpal.androidterm.emulatorview.EmulatorView; import jackpal.androidterm.emulatorview.TermSession; import jackpal.androidterm.emulatorview.UpdateCallback; import jackpal.androidterm.emulatorview.compat.ClipboardManagerCompat; import jackpal.androidterm.emulatorview.compat.ClipboardManagerCompatFactory; import java.io.UnsupportedEncodingException; import java.text.Collator; import java.util.Arrays; import java.util.Locale; import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.net.Uri; import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.PowerManager; import android.preference.PreferenceManager; import android.util.DisplayMetrics; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.Gravity; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.TextView; import android.widget.Toast; import com.adblog.compat.ActionBarCompat; import com.adblog.compat.ActivityCompat; import com.adblog.compat.AndroidCompat; import com.adblog.compat.MenuItemCompat; import com.adblog.control.Util; import com.adblog.util.SessionList; import com.adblog.util.TermSettings; import com.tool.androidesk.R; /** * A terminal emulator activity. */ public class Term extends Activity implements UpdateCallback { /** * The ViewFlipper which holds the collection of EmulatorView widgets. */ private TermViewFlipper mViewFlipper; /** * The name of the ViewFlipper in the resources. */ private static final int VIEW_FLIPPER = R.id.view_flipper; private SessionList mTermSessions; private SharedPreferences mPrefs; private TermSettings mSettings; private final static int SELECT_TEXT_ID = 0; private final static int COPY_ALL_ID = 1; private final static int PASTE_ID = 2; private final static int SEND_CONTROL_KEY_ID = 3; private final static int SEND_FN_KEY_ID = 4; private boolean mAlreadyStarted = false; private boolean mStopServiceOnFinish = false; private Intent TSIntent; public static final int REQUEST_CHOOSE_WINDOW = 1; public static final String EXTRA_WINDOW_ID = "jackpal.androidterm.window_id"; private int onResumeSelectWindow = -1; private PowerManager.WakeLock mWakeLock; private WifiManager.WifiLock mWifiLock; // Available on API 12 and later private static final int WIFI_MODE_FULL_HIGH_PERF = 3; private boolean mBackKeyPressed; private static final String ACTION_PATH_BROADCAST = "jackpal.androidterm.broadcast.APPEND_TO_PATH"; private static final String ACTION_PATH_PREPEND_BROADCAST = "jackpal.androidterm.broadcast.PREPEND_TO_PATH"; private static final String PERMISSION_PATH_BROADCAST = "jackpal.androidterm.permission.APPEND_TO_PATH"; private static final String PERMISSION_PATH_PREPEND_BROADCAST = "jackpal.androidterm.permission.PREPEND_TO_PATH"; private int mPendingPathBroadcasts = 0; private BroadcastReceiver mPathReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String path = makePathFromBundle(getResultExtras(false)); if (intent.getAction().equals(ACTION_PATH_PREPEND_BROADCAST)) { mSettings.setPrependPath(path); } else { mSettings.setAppendPath(path); } mPendingPathBroadcasts--; if (mPendingPathBroadcasts <= 0 && mTermService != null) { populateViewFlipper(); populateWindowList(); } } }; // Available on API 12 and later private static final int FLAG_INCLUDE_STOPPED_PACKAGES = 0x20; private TermService mTermService; private ServiceConnection mTSConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { Log.i(TermDebug.LOG_TAG, "Bound to TermService"); TermService.TSBinder binder = (TermService.TSBinder) service; mTermService = binder.getService(); if (mPendingPathBroadcasts <= 0) { populateViewFlipper(); populateWindowList(); } } public void onServiceDisconnected(ComponentName arg0) { mTermService = null; } }; private ActionBarCompat mActionBar; private int mActionBarMode = TermSettings.ACTION_BAR_MODE_NONE; private WindowListAdapter mWinListAdapter; private class WindowListActionBarAdapter extends WindowListAdapter implements UpdateCallback { // From android.R.style in API 13 private static final int TextAppearance_Holo_Widget_ActionBar_Title = 0x01030112; public WindowListActionBarAdapter(SessionList sessions) { super(sessions); } @Override public View getView(int position, View convertView, ViewGroup parent) { TextView label = new TextView(Term.this); String title = getSessionTitle(position, getString(R.string.window_title, position + 1)); label.setText(title); if (AndroidCompat.SDK >= 13) { label.setTextAppearance(Term.this, TextAppearance_Holo_Widget_ActionBar_Title); } else { label.setTextAppearance(Term.this, android.R.style.TextAppearance_Medium); } return label; } @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { return super.getView(position, convertView, parent); } public void onUpdate() { notifyDataSetChanged(); mActionBar.setSelectedNavigationItem(mViewFlipper .getDisplayedChild()); } } private ActionBarCompat.OnNavigationListener mWinListItemSelected = new ActionBarCompat.OnNavigationListener() { public boolean onNavigationItemSelected(int position, long id) { int oldPosition = mViewFlipper.getDisplayedChild(); if (position != oldPosition) { mViewFlipper.setDisplayedChild(position); if (mActionBarMode == TermSettings.ACTION_BAR_MODE_HIDES) { mActionBar.hide(); } } return true; } }; private boolean mHaveFullHwKeyboard = false; private class EmulatorViewGestureListener extends SimpleOnGestureListener { private EmulatorView view; public EmulatorViewGestureListener(EmulatorView view) { this.view = view; } @Override public boolean onSingleTapUp(MotionEvent e) { doUIToggle((int) e.getX(), (int) e.getY(), view.getVisibleWidth(), view.getVisibleHeight()); return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { float absVelocityX = Math.abs(velocityX); float absVelocityY = Math.abs(velocityY); if (absVelocityX > Math.max(1000.0f, 2.0 * absVelocityY)) { // Assume user wanted side to side movement if (velocityX > 0) { // Left to right swipe -- previous window mViewFlipper.showPrevious(); } else { // Right to left swipe -- next window mViewFlipper.showNext(); } return true; } else { return false; } } } private View.OnKeyListener mBackKeyListener = new View.OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && mActionBarMode == TermSettings.ACTION_BAR_MODE_HIDES && mActionBar.isShowing()) { /* * We need to intercept the key event before the view sees it, * otherwise the view will handle it before we get it */ onKeyUp(keyCode, event); return true; } else { return false; } } }; private Handler mHandler = new Handler(); @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); Log.e(TermDebug.LOG_TAG, "onCreate"); mPrefs = PreferenceManager.getDefaultSharedPreferences(this); mSettings = new TermSettings(getResources(), mPrefs); Intent broadcast = new Intent(ACTION_PATH_BROADCAST); if (AndroidCompat.SDK >= 12) { broadcast.addFlags(FLAG_INCLUDE_STOPPED_PACKAGES); } mPendingPathBroadcasts++; sendOrderedBroadcast(broadcast, PERMISSION_PATH_BROADCAST, mPathReceiver, null, RESULT_OK, null, null); broadcast = new Intent(broadcast); broadcast.setAction(ACTION_PATH_PREPEND_BROADCAST); mPendingPathBroadcasts++; sendOrderedBroadcast(broadcast, PERMISSION_PATH_PREPEND_BROADCAST, mPathReceiver, null, RESULT_OK, null, null); TSIntent = new Intent(this, TermService.class); startService(TSIntent); if (!bindService(TSIntent, mTSConnection, BIND_AUTO_CREATE)) { Log.w(TermDebug.LOG_TAG, "bind to service failed!"); } if (AndroidCompat.SDK >= 11) { int actionBarMode = mSettings.actionBarMode(); mActionBarMode = actionBarMode; switch (actionBarMode) { case TermSettings.ACTION_BAR_MODE_ALWAYS_VISIBLE: setTheme(R.style.Theme_Holo); break; case TermSettings.ACTION_BAR_MODE_HIDES: setTheme(R.style.Theme_Holo_ActionBarOverlay); break; } } setContentView(R.layout.term_activity); mViewFlipper = (TermViewFlipper) findViewById(VIEW_FLIPPER); PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TermDebug.LOG_TAG); WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE); int wifiLockMode = WifiManager.WIFI_MODE_FULL; if (AndroidCompat.SDK >= 12) { wifiLockMode = WIFI_MODE_FULL_HIGH_PERF; } mWifiLock = wm.createWifiLock(wifiLockMode, TermDebug.LOG_TAG); ActionBarCompat actionBar = ActivityCompat.getActionBar(this); if (actionBar != null) { mActionBar = actionBar; actionBar.setNavigationMode(ActionBarCompat.NAVIGATION_MODE_LIST); actionBar.setDisplayOptions(0, ActionBarCompat.DISPLAY_SHOW_TITLE); if (mActionBarMode == TermSettings.ACTION_BAR_MODE_HIDES) { actionBar.hide(); } } mHaveFullHwKeyboard = checkHaveFullHwKeyboard(getResources() .getConfiguration()); updatePrefs(); mAlreadyStarted = true; } private String makePathFromBundle(Bundle extras) { if (extras == null || extras.size() == 0) { return ""; } String[] keys = new String[extras.size()]; keys = extras.keySet().toArray(keys); Collator collator = Collator.getInstance(Locale.US); Arrays.sort(keys, collator); StringBuilder path = new StringBuilder(); for (String key : keys) { String dir = extras.getString(key); if (dir != null && !dir.equals("")) { path.append(dir); path.append(":"); } } return path.substring(0, path.length() - 1); } private void populateViewFlipper() { if (mTermService != null) { mTermSessions = mTermService.getSessions(); mTermSessions.addCallback(this); if (mTermSessions.size() == 0) { mTermSessions.add(createTermSession()); } for (TermSession session : mTermSessions) { EmulatorView view = createEmulatorView(session); mViewFlipper.addView(view); } updatePrefs(); Intent intent = getIntent(); int flags = intent.getFlags(); String action = intent.getAction(); if ((flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0 && action != null) { if (action.equals(RemoteInterface.PRIVACT_OPEN_NEW_WINDOW)) { mViewFlipper.setDisplayedChild(mTermSessions.size() - 1); } else if (action.equals(RemoteInterface.PRIVACT_SWITCH_WINDOW)) { int target = intent.getIntExtra( RemoteInterface.PRIVEXTRA_TARGET_WINDOW, -1); if (target >= 0) { mViewFlipper.setDisplayedChild(target); } } } mViewFlipper.resumeCurrentView(); } } private void populateWindowList() { if (mActionBar == null) { // Not needed return; } if (mTermSessions != null) { int position = mViewFlipper.getDisplayedChild(); WindowListAdapter adapter = mWinListAdapter; if (adapter == null) { adapter = new WindowListActionBarAdapter(mTermSessions); mWinListAdapter = adapter; SessionList sessions = mTermSessions; sessions.addCallback(adapter); sessions.addTitleChangedListener(adapter); mViewFlipper.addCallback(adapter); mActionBar.setListNavigationCallbacks(adapter, mWinListItemSelected); } else { adapter.setSessions(mTermSessions); } mActionBar.setSelectedNavigationItem(position); } } @Override public void onDestroy() { super.onDestroy(); mViewFlipper.removeAllViews(); unbindService(mTSConnection); if (mStopServiceOnFinish) { stopService(TSIntent); } mTermService = null; mTSConnection = null; if (mWakeLock.isHeld()) { mWakeLock.release(); } if (mWifiLock.isHeld()) { mWifiLock.release(); } } private void restart() { startActivity(getIntent()); finish(); } protected static TermSession createTermSession(Context context, TermSettings settings, String initialCommand) { ShellTermSession session = new ShellTermSession(settings, initialCommand); // XXX We should really be able to fetch this from within TermSession session.setProcessExitMessage(context .getString(R.string.process_exit_message)); return session; } private TermSession createTermSession() { TermSettings settings = mSettings; TermSession session = createTermSession(this, settings, settings.getInitialCommand()); session.setFinishCallback(mTermService); return session; } private TermView createEmulatorView(TermSession session) { DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); TermView emulatorView = new TermView(this, session, metrics); emulatorView.setExtGestureListener(new EmulatorViewGestureListener( emulatorView)); emulatorView.setOnKeyListener(mBackKeyListener); registerForContextMenu(emulatorView); return emulatorView; } private TermSession getCurrentTermSession() { SessionList sessions = mTermSessions; if (sessions == null) { return null; } else { return sessions.get(mViewFlipper.getDisplayedChild()); } } private EmulatorView getCurrentEmulatorView() { return (EmulatorView) mViewFlipper.getCurrentView(); } private void updatePrefs() { DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); ColorScheme colorScheme = new ColorScheme(mSettings.getColorScheme()); mViewFlipper.updatePrefs(mSettings); for (View v : mViewFlipper) { ((EmulatorView) v).setDensity(metrics); ((TermView) v).updatePrefs(mSettings); } if (mTermSessions != null) { for (TermSession session : mTermSessions) { ((ShellTermSession) session).updatePrefs(mSettings); } } { Window win = getWindow(); WindowManager.LayoutParams params = win.getAttributes(); final int FULLSCREEN = WindowManager.LayoutParams.FLAG_FULLSCREEN; int desiredFlag = mSettings.showStatusBar() ? 0 : FULLSCREEN; if (desiredFlag != (params.flags & FULLSCREEN) || (AndroidCompat.SDK >= 11 && mActionBarMode != mSettings .actionBarMode())) { if (mAlreadyStarted) { // Can't switch to/from fullscreen after // starting the activity. restart(); } else { win.setFlags(desiredFlag, FULLSCREEN); if (mActionBarMode == TermSettings.ACTION_BAR_MODE_HIDES) { mActionBar.hide(); } } } } int orientation = mSettings.getScreenOrientation(); int o = 0; if (orientation == 0) { o = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; } else if (orientation == 1) { o = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; } else if (orientation == 2) { o = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; } else { /* Shouldn't be happened. */ } setRequestedOrientation(o); } @Override public void onResume() { super.onResume(); SessionList sessions = mTermSessions; TermViewFlipper viewFlipper = mViewFlipper; if (sessions != null) { sessions.addCallback(this); WindowListAdapter adapter = mWinListAdapter; if (adapter != null) { sessions.addCallback(adapter); sessions.addTitleChangedListener(adapter); viewFlipper.addCallback(adapter); } } if (sessions != null && sessions.size() < viewFlipper.getChildCount()) { for (int i = 0; i < viewFlipper.getChildCount(); ++i) { EmulatorView v = (EmulatorView) viewFlipper.getChildAt(i); if (!sessions.contains(v.getTermSession())) { v.onPause(); viewFlipper.removeView(v); --i; } } } mPrefs = PreferenceManager.getDefaultSharedPreferences(this); // the HOME dir needs to be set here since it comes from Context SharedPreferences.Editor editor = mPrefs.edit(); String defValue = getDir("HOME", MODE_PRIVATE).getAbsolutePath(); String homePath = mPrefs.getString("home_path", defValue); editor.putString("home_path", homePath); editor.commit(); mSettings.readPrefs(mPrefs); updatePrefs(); if (onResumeSelectWindow >= 0) { viewFlipper.setDisplayedChild(onResumeSelectWindow); onResumeSelectWindow = -1; } viewFlipper.onResume(); } @Override public void onPause() { super.onPause(); SessionList sessions = mTermSessions; TermViewFlipper viewFlipper = mViewFlipper; viewFlipper.onPause(); if (sessions != null) { sessions.removeCallback(this); WindowListAdapter adapter = mWinListAdapter; if (adapter != null) { sessions.removeCallback(adapter); sessions.removeTitleChangedListener(adapter); viewFlipper.removeCallback(adapter); } } if (AndroidCompat.SDK < 5) { /* * If we lose focus between a back key down and a back key up, we * shouldn't respond to the next back key up event unless we get * another key down first */ mBackKeyPressed = false; } /* * Explicitly close the input method Otherwise, the soft keyboard could * cover up whatever activity takes our place */ final IBinder token = viewFlipper.getWindowToken(); new Thread() { @Override public void run() { InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(token, 0); } }.start(); } private boolean checkHaveFullHwKeyboard(Configuration c) { return (c.keyboard == Configuration.KEYBOARD_QWERTY) && (c.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mHaveFullHwKeyboard = checkHaveFullHwKeyboard(newConfig); EmulatorView v = (EmulatorView) mViewFlipper.getCurrentView(); if (v != null) { v.updateSize(false); } if (mWinListAdapter != null) { // Force Android to redraw the label in the navigation dropdown mWinListAdapter.notifyDataSetChanged(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); MenuItemCompat.setShowAsAction(menu.findItem(R.id.menu_new_window), MenuItemCompat.SHOW_AS_ACTION_ALWAYS); MenuItemCompat.setShowAsAction(menu.findItem(R.id.menu_close_window), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.menu_preferences) { doPreferences(); } else if (id == R.id.menu_new_window) { doCreateNewWindow(); } else if (id == R.id.menu_close_window) { confirmCloseWindow(); } else if (id == R.id.menu_window_list) { startActivityForResult(new Intent(this, WindowList.class), REQUEST_CHOOSE_WINDOW); } else if (id == R.id.menu_reset) { doResetTerminal(); Toast toast = Toast.makeText(this, R.string.reset_toast_notification, Toast.LENGTH_LONG); toast.setGravity(Gravity.CENTER, 0, 0); toast.show(); } else if (id == R.id.menu_send_email) { doEmailTranscript(); } else if (id == R.id.menu_special_keys) { doDocumentKeys(); } else if (id == R.id.menu_toggle_soft_keyboard) { doToggleSoftKeyboard(); } // else if (id == R.id.menu_toggle_wakelock) { // doToggleWakeLock(); // } else if (id == R.id.menu_toggle_wifilock) { // doToggleWifiLock(); // } // Hide the action bar if appropriate if (mActionBarMode == TermSettings.ACTION_BAR_MODE_HIDES) { mActionBar.hide(); } return super.onOptionsItemSelected(item); } private void doCreateNewWindow() { if (mTermSessions == null) { Log.w(TermDebug.LOG_TAG, "Couldn't create new window because mTermSessions == null"); return; } TermSession session = createTermSession(); mTermSessions.add(session); TermView view = createEmulatorView(session); view.updatePrefs(mSettings); mViewFlipper.addView(view); mViewFlipper.setDisplayedChild(mViewFlipper.getChildCount() - 1); } private void confirmCloseWindow() { final AlertDialog.Builder b = new AlertDialog.Builder(this); b.setIcon(android.R.drawable.ic_dialog_alert); b.setMessage(R.string.confirm_window_close_message); final Runnable closeWindow = new Runnable() { public void run() { doCloseWindow(); } }; b.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.dismiss(); mHandler.post(closeWindow); } }); b.setNegativeButton(android.R.string.no, null); b.show(); } private void doCloseWindow() { if (mTermSessions == null) { return; } EmulatorView view = getCurrentEmulatorView(); if (view == null) { return; } TermSession session = mTermSessions.remove(mViewFlipper .getDisplayedChild()); view.onPause(); session.finish(); mViewFlipper.removeView(view); if (mTermSessions.size() == 0) { mStopServiceOnFinish = true; finish(); } else { mViewFlipper.showNext(); } } @Override protected void onActivityResult(int request, int result, Intent data) { switch (request) { case REQUEST_CHOOSE_WINDOW: if (result == RESULT_OK && data != null) { int position = data.getIntExtra(EXTRA_WINDOW_ID, -2); if (position >= 0) { // Switch windows after session list is in sync, not here onResumeSelectWindow = position; } else if (position == -1) { doCreateNewWindow(); onResumeSelectWindow = mTermSessions.size() - 1; } } else { // Close the activity if user closed all sessions if (mTermSessions == null || mTermSessions.size() == 0) { mStopServiceOnFinish = true; finish(); } } break; } } @Override protected void onNewIntent(Intent intent) { if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) { // Don't repeat action if intent comes from history return; } String action = intent.getAction(); if (action == null) { return; } if (action.equals(RemoteInterface.PRIVACT_OPEN_NEW_WINDOW)) { // New session was created, add an EmulatorView to match SessionList sessions = mTermSessions; if (sessions == null) { // Presumably populateViewFlipper() will do this later ... return; } int position = sessions.size() - 1; TermSession session = sessions.get(position); EmulatorView view = createEmulatorView(session); mViewFlipper.addView(view); onResumeSelectWindow = position; } else if (action.equals(RemoteInterface.PRIVACT_SWITCH_WINDOW)) { int target = intent.getIntExtra( RemoteInterface.PRIVEXTRA_TARGET_WINDOW, -1); if (target >= 0) { onResumeSelectWindow = target; } } } @Override public boolean onPrepareOptionsMenu(Menu menu) { // MenuItem wakeLockItem = menu.findItem(R.id.menu_toggle_wakelock); // MenuItem wifiLockItem = menu.findItem(R.id.menu_toggle_wifilock); // if (mWakeLock.isHeld()) { // wakeLockItem.setTitle(R.string.disable_wakelock); // } else { // wakeLockItem.setTitle(R.string.enable_wakelock); // } // if (mWifiLock.isHeld()) { // wifiLockItem.setTitle(R.string.disable_wifilock); // } else { // wifiLockItem.setTitle(R.string.enable_wifilock); // } return super.onPrepareOptionsMenu(menu); } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); menu.setHeaderTitle(R.string.edit_text); menu.add(0, SELECT_TEXT_ID, 0, R.string.select_text); menu.add(0, COPY_ALL_ID, 0, R.string.copy_all); menu.add(0, PASTE_ID, 0, R.string.paste); menu.add(0, SEND_CONTROL_KEY_ID, 0, R.string.send_control_key); menu.add(0, SEND_FN_KEY_ID, 0, R.string.send_fn_key); if (!canPaste()) { menu.getItem(PASTE_ID).setEnabled(false); } } @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { case SELECT_TEXT_ID: getCurrentEmulatorView().toggleSelectingText(); return true; case COPY_ALL_ID: doCopyAll(); return true; case PASTE_ID: doPaste(); return true; case SEND_CONTROL_KEY_ID: doSendControlKey(); return true; case SEND_FN_KEY_ID: doSendFnKey(); return true; default: return super.onContextItemSelected(item); } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { /* * The pre-Eclair default implementation of onKeyDown() would prevent * our handling of the Back key in onKeyUp() from taking effect, so * ignore it here */ if (AndroidCompat.SDK < 5 && keyCode == KeyEvent.KEYCODE_BACK) { /* * Android pre-Eclair has no key event tracking, and a back key down * event delivered to an activity above us in the back stack could * be succeeded by a back key up event to us, so we need to keep * track of our own back key presses */ mBackKeyPressed = true; return true; } else { return super.onKeyDown(keyCode, event); } } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_BACK: if (AndroidCompat.SDK < 5) { if (!mBackKeyPressed) { /* * This key up event might correspond to a key down * delivered to another activity -- ignore */ return false; } mBackKeyPressed = false; } if (mActionBarMode == TermSettings.ACTION_BAR_MODE_HIDES && mActionBar.isShowing()) { mActionBar.hide(); return true; } switch (mSettings.getBackKeyAction()) { case TermSettings.BACK_KEY_STOPS_SERVICE: mStopServiceOnFinish = true; case TermSettings.BACK_KEY_CLOSES_ACTIVITY: finish(); return true; case TermSettings.BACK_KEY_CLOSES_WINDOW: doCloseWindow(); return true; default: return false; } case KeyEvent.KEYCODE_MENU: if (mActionBar != null && !mActionBar.isShowing()) { mActionBar.show(); return true; } else { return super.onKeyUp(keyCode, event); } default: return super.onKeyUp(keyCode, event); } } // Called when the list of sessions changes public void onUpdate() { SessionList sessions = mTermSessions; if (sessions == null) { return; } if (sessions.size() == 0) { mStopServiceOnFinish = true; finish(); } else if (sessions.size() < mViewFlipper.getChildCount()) { for (int i = 0; i < mViewFlipper.getChildCount(); ++i) { EmulatorView v = (EmulatorView) mViewFlipper.getChildAt(i); if (!sessions.contains(v.getTermSession())) { v.onPause(); mViewFlipper.removeView(v); --i; } } } } private boolean canPaste() { ClipboardManagerCompat clip = ClipboardManagerCompatFactory .getManager(getApplicationContext()); if (clip.hasText()) { return true; } return false; } private void doPreferences() { startActivity(new Intent(this, TermPreferences.class)); } private void doResetTerminal() { TermSession session = getCurrentTermSession(); if (session != null) { session.reset(); } } private void doEmailTranscript() { TermSession session = getCurrentTermSession(); if (session != null) { // Don't really want to supply an address, but // currently it's required, otherwise nobody // wants to handle the intent. String addr = "logcatlog@gmail.com"; Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:" + addr)); String subject = getString(R.string.email_transcript_subject); String title = session.getTitle(); if (title != null) { subject = subject + " - " + title; } intent.putExtra(Intent.EXTRA_SUBJECT, subject+" "+Util.getTitle()); intent.putExtra(Intent.EXTRA_TEXT, Util.getDevicesInfo(getBaseContext()) + "\n" + session.getTranscriptText().trim()); try { startActivity(Intent.createChooser(intent, getString(R.string.email_transcript_chooser_title))); } catch (ActivityNotFoundException e) { Toast.makeText(this, R.string.email_transcript_no_email_activity_found, Toast.LENGTH_LONG).show(); } } } private void doCopyAll() { ClipboardManagerCompat clip = ClipboardManagerCompatFactory .getManager(getApplicationContext()); clip.setText(getCurrentTermSession().getTranscriptText().trim()); } private void doPaste() { ClipboardManagerCompat clip = ClipboardManagerCompatFactory .getManager(getApplicationContext()); CharSequence paste = clip.getText(); byte[] utf8; try { utf8 = paste.toString().getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { Log.e(TermDebug.LOG_TAG, "UTF-8 encoding not found."); return; } getCurrentTermSession().write(paste.toString()); } private void doSendControlKey() { getCurrentEmulatorView().sendControlKey(); } private void doSendFnKey() { getCurrentEmulatorView().sendFnKey(); } private void doDocumentKeys() { AlertDialog.Builder dialog = new AlertDialog.Builder(this); Resources r = getResources(); dialog.setTitle(r.getString(R.string.control_key_dialog_title)); dialog.setMessage(formatMessage(mSettings.getControlKeyId(), TermSettings.CONTROL_KEY_ID_NONE, r, R.array.control_keys_short_names, R.string.control_key_dialog_control_text, R.string.control_key_dialog_control_disabled_text, "CTRLKEY") + "\n\n" + formatMessage(mSettings.getFnKeyId(), TermSettings.FN_KEY_ID_NONE, r, R.array.fn_keys_short_names, R.string.control_key_dialog_fn_text, R.string.control_key_dialog_fn_disabled_text, "FNKEY")); dialog.show(); } private String formatMessage(int keyId, int disabledKeyId, Resources r, int arrayId, int enabledId, int disabledId, String regex) { if (keyId == disabledKeyId) { return r.getString(disabledId); } String[] keyNames = r.getStringArray(arrayId); String keyName = keyNames[keyId]; String template = r.getString(enabledId); String result = template.replaceAll(regex, keyName); return result; } private void doToggleSoftKeyboard() { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); } private void doToggleWakeLock() { if (mWakeLock.isHeld()) { mWakeLock.release(); } else { mWakeLock.acquire(); } ActivityCompat.invalidateOptionsMenu(this); } private void doToggleWifiLock() { if (mWifiLock.isHeld()) { mWifiLock.release(); } else { mWifiLock.acquire(); } ActivityCompat.invalidateOptionsMenu(this); } private void doToggleActionBar() { ActionBarCompat bar = mActionBar; if (bar == null) { return; } if (bar.isShowing()) { bar.hide(); } else { bar.show(); } } private void doUIToggle(int x, int y, int width, int height) { switch (mActionBarMode) { case TermSettings.ACTION_BAR_MODE_NONE: if (AndroidCompat.SDK >= 11 && (mHaveFullHwKeyboard || y < height / 2)) { openOptionsMenu(); return; } else { doToggleSoftKeyboard(); } break; case TermSettings.ACTION_BAR_MODE_ALWAYS_VISIBLE: if (!mHaveFullHwKeyboard) { doToggleSoftKeyboard(); } break; case TermSettings.ACTION_BAR_MODE_HIDES: if (mHaveFullHwKeyboard || y < height / 2) { doToggleActionBar(); return; } else { doToggleSoftKeyboard(); } break; } getCurrentEmulatorView().requestFocus(); } }