0
|
1 /*
|
|
2 * ConnectBot: simple, powerful, open-source SSH client for Android
|
|
3 * Copyright 2007 Kenny Root, Jeffrey Sharkey
|
|
4 *
|
|
5 * Licensed under the Apache License, Version 2.0 (the "License");
|
|
6 * you may not use this file except in compliance with the License.
|
|
7 * You may obtain a copy of the License at
|
|
8 *
|
|
9 * http://www.apache.org/licenses/LICENSE-2.0
|
|
10 *
|
|
11 * Unless required by applicable law or agreed to in writing, software
|
|
12 * distributed under the License is distributed on an "AS IS" BASIS,
|
|
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14 * See the License for the specific language governing permissions and
|
|
15 * limitations under the License.
|
|
16 */
|
|
17
|
|
18 package com.five_ten_sg.connectbot;
|
|
19
|
|
20 import java.io.File;
|
|
21 import java.net.URI;
|
|
22 import java.util.ArrayList;
|
|
23 import java.util.List;
|
|
24
|
|
25 import com.five_ten_sg.connectbot.bean.SelectionArea;
|
|
26 import com.five_ten_sg.connectbot.service.PromptHelper;
|
|
27 import com.five_ten_sg.connectbot.service.TerminalBridge;
|
|
28 import com.five_ten_sg.connectbot.service.TerminalKeyListener;
|
|
29 import com.five_ten_sg.connectbot.service.TerminalManager;
|
|
30 import com.five_ten_sg.connectbot.util.FileChooser;
|
|
31 import com.five_ten_sg.connectbot.util.FileChooserCallback;
|
|
32 import com.five_ten_sg.connectbot.util.PreferenceConstants;
|
|
33 import com.five_ten_sg.connectbot.util.TransferThread;
|
|
34 import android.app.Activity;
|
|
35 import android.app.AlertDialog;
|
|
36 import android.content.ComponentName;
|
|
37 import android.content.Context;
|
|
38 import android.content.DialogInterface;
|
|
39 import android.content.Intent;
|
|
40 import android.content.ServiceConnection;
|
|
41 import android.content.SharedPreferences;
|
|
42 import android.content.pm.ActivityInfo;
|
|
43 import android.content.res.Configuration;
|
|
44 import android.media.AudioManager;
|
|
45 import android.net.Uri;
|
|
46 import android.os.Bundle;
|
|
47 import android.os.Handler;
|
|
48 import android.os.IBinder;
|
|
49 import android.os.Message;
|
|
50 import android.preference.PreferenceManager;
|
|
51 import android.text.ClipboardManager;
|
|
52 import android.text.InputType;
|
|
53 import android.text.method.PasswordTransformationMethod;
|
|
54 import android.text.method.SingleLineTransformationMethod;
|
|
55 import android.util.FloatMath;
|
|
56 import android.util.Log;
|
|
57 import android.view.GestureDetector;
|
|
58 import android.view.KeyEvent;
|
|
59 import android.view.LayoutInflater;
|
|
60 import android.view.Menu;
|
|
61 import android.view.MenuItem;
|
|
62 import android.view.MenuItem.OnMenuItemClickListener;
|
|
63 import android.view.MotionEvent;
|
|
64 import android.view.View;
|
|
65 import android.view.View.OnClickListener;
|
|
66 import android.view.View.OnKeyListener;
|
|
67 import android.view.View.OnLongClickListener;
|
|
68 import android.view.View.OnTouchListener;
|
|
69 import android.view.ViewConfiguration;
|
|
70 import android.view.WindowManager;
|
|
71 import android.view.animation.Animation;
|
|
72 import android.view.animation.AnimationUtils;
|
|
73 import android.view.inputmethod.InputMethodManager;
|
|
74 import android.widget.Button;
|
|
75 import android.widget.EditText;
|
|
76 import android.widget.ImageView;
|
|
77 import android.widget.RelativeLayout;
|
|
78 import android.widget.TextView;
|
|
79 import android.widget.Toast;
|
|
80 import android.widget.ViewFlipper;
|
|
81 import de.mud.terminal.vt320;
|
|
82
|
|
83 public class ConsoleActivity extends Activity implements FileChooserCallback {
|
|
84 public final static String TAG = "ConnectBot.ConsoleActivity";
|
|
85
|
|
86 protected static final int REQUEST_EDIT = 1;
|
|
87
|
|
88 private static final int CLICK_TIME = 400;
|
|
89 private static final float MAX_CLICK_DISTANCE = 25f;
|
|
90 private static final int KEYBOARD_DISPLAY_TIME = 1500;
|
|
91
|
|
92 // Direction to shift the ViewFlipper
|
|
93 private static final int SHIFT_LEFT = 0;
|
|
94 private static final int SHIFT_RIGHT = 1;
|
|
95
|
|
96 protected ViewFlipper flip = null;
|
|
97 protected TerminalManager bound = null;
|
|
98 protected LayoutInflater inflater = null;
|
|
99
|
|
100 private SharedPreferences prefs = null;
|
|
101
|
|
102 // determines whether or not menuitem accelerators are bound
|
|
103 // otherwise they collide with an external keyboard's CTRL-char
|
|
104 private boolean hardKeyboard = false;
|
|
105
|
|
106 // determines whether we are in the fullscreen mode
|
|
107 private static final int FULLSCREEN_ON = 1;
|
|
108 private static final int FULLSCREEN_OFF = 2;
|
|
109
|
|
110 private int fullScreen;
|
|
111
|
|
112 protected Uri requested;
|
|
113
|
|
114 protected ClipboardManager clipboard;
|
|
115 private RelativeLayout stringPromptGroup;
|
|
116 protected EditText stringPrompt;
|
|
117 private TextView stringPromptInstructions;
|
|
118
|
|
119 private RelativeLayout booleanPromptGroup;
|
|
120 private TextView booleanPrompt;
|
|
121 private Button booleanYes, booleanNo;
|
|
122
|
|
123 private RelativeLayout keyboardGroup;
|
|
124 private Runnable keyboardGroupHider;
|
|
125
|
|
126 private TextView empty;
|
|
127
|
|
128 private Animation slide_left_in, slide_left_out, slide_right_in, slide_right_out, fade_stay_hidden, fade_out_delayed;
|
|
129
|
|
130 private Animation keyboard_fade_in, keyboard_fade_out;
|
|
131 private float lastX, lastY;
|
|
132
|
|
133 private InputMethodManager inputManager;
|
|
134
|
|
135 private MenuItem disconnect, copy, paste, portForward, resize, urlscan, screenCapture, download, upload;
|
|
136
|
|
137 protected TerminalBridge copySource = null;
|
|
138 private int lastTouchRow, lastTouchCol;
|
|
139
|
|
140 private boolean forcedOrientation;
|
|
141
|
|
142 private Handler handler = new Handler();
|
|
143
|
|
144 private ImageView mKeyboardButton;
|
|
145
|
|
146 private ActionBarWrapper actionBar;
|
|
147 private boolean inActionBarMenu = false;
|
|
148
|
|
149 private ServiceConnection connection = new ServiceConnection() {
|
|
150 public void onServiceConnected(ComponentName className, IBinder service) {
|
|
151 bound = ((TerminalManager.TerminalBinder) service).getService();
|
|
152 // let manager know about our event handling services
|
|
153 bound.disconnectHandler = disconnectHandler;
|
|
154 Log.d(TAG, String.format("Connected to TerminalManager and found bridges.size=%d", bound.bridges.size()));
|
|
155 bound.setResizeAllowed(true);
|
|
156 bound.hardKeyboardHidden = (getResources().getConfiguration().hardKeyboardHidden ==
|
|
157 Configuration.HARDKEYBOARDHIDDEN_YES);
|
|
158
|
|
159 // set fullscreen value
|
|
160 if (bound.getFullScreen() == 0) {
|
|
161 setFullScreen(FULLSCREEN_OFF);
|
|
162 }
|
|
163 else if (fullScreen != bound.getFullScreen())
|
|
164 setFullScreen(bound.getFullScreen());
|
|
165
|
|
166 // clear out any existing bridges and record requested index
|
|
167 flip.removeAllViews();
|
|
168 final String requestedNickname = (requested != null) ? requested.getFragment() : null;
|
|
169 int requestedIndex = -1;
|
|
170 TerminalBridge requestedBridge = bound.getConnectedBridge(requestedNickname);
|
|
171
|
|
172 // If we didn't find the requested connection, try opening it
|
|
173 if (requestedNickname != null && requestedBridge == null) {
|
|
174 try {
|
|
175 Log.d(TAG, String.format("We couldnt find an existing bridge with URI=%s (nickname=%s), so creating one now", requested.toString(), requestedNickname));
|
|
176 requestedBridge = bound.openConnection(requested);
|
|
177 }
|
|
178 catch (Exception e) {
|
|
179 Log.e(TAG, "Problem while trying to create new requested bridge from URI", e);
|
|
180 }
|
|
181 }
|
|
182
|
|
183 // create views for all bridges on this service
|
|
184 for (TerminalBridge bridge : bound.bridges) {
|
|
185 final int currentIndex = addNewTerminalView(bridge);
|
|
186
|
|
187 // check to see if this bridge was requested
|
|
188 if (bridge == requestedBridge) {
|
|
189 requestedIndex = currentIndex;
|
|
190 // store this bridge as default bridge
|
|
191 bound.defaultBridge = bridge;
|
|
192 }
|
|
193 }
|
|
194
|
|
195 // if no bridge was requested, try using default bridge
|
|
196 if (requestedIndex < 0) {
|
|
197 requestedIndex = getFlipIndex(bound.defaultBridge);
|
|
198
|
|
199 if (requestedIndex < 0)
|
|
200 requestedIndex = 0;
|
|
201 }
|
|
202
|
|
203 setDisplayedTerminal(requestedIndex);
|
|
204 }
|
|
205 public void onServiceDisconnected(ComponentName className) {
|
|
206 // tell each bridge to forget about our prompt handler
|
|
207 synchronized (bound.bridges) {
|
|
208 for (TerminalBridge bridge : bound.bridges)
|
|
209 bridge.promptHelper.setHandler(null);
|
|
210 }
|
|
211
|
|
212 flip.removeAllViews();
|
|
213 updateEmptyVisible();
|
|
214 bound = null;
|
|
215 }
|
|
216 };
|
|
217
|
|
218 protected Handler promptHandler = new Handler() {
|
|
219 @Override
|
|
220 public void handleMessage(Message msg) {
|
|
221 // someone below us requested to display a prompt
|
|
222 updatePromptVisible();
|
|
223 }
|
|
224 };
|
|
225
|
|
226 protected Handler disconnectHandler = new Handler() {
|
|
227 @Override
|
|
228 public void handleMessage(Message msg) {
|
|
229 Log.d(TAG, "Someone sending HANDLE_DISCONNECT to parentHandler");
|
|
230 // someone below us requested to display a password dialog
|
|
231 // they are sending nickname and requested
|
|
232 TerminalBridge bridge = (TerminalBridge)msg.obj;
|
|
233
|
|
234 if (bridge.isAwaitingClose())
|
|
235 closeBridge(bridge);
|
|
236 }
|
|
237 };
|
|
238
|
|
239 /**
|
|
240 * @param bridge
|
|
241 */
|
|
242 private void closeBridge(final TerminalBridge bridge) {
|
|
243 synchronized (flip) {
|
|
244 final int flipIndex = getFlipIndex(bridge);
|
|
245
|
|
246 if (flipIndex >= 0) {
|
|
247 if (flip.getDisplayedChild() == flipIndex) {
|
|
248 shiftCurrentTerminal(SHIFT_LEFT);
|
|
249 }
|
|
250
|
|
251 flip.removeViewAt(flipIndex);
|
|
252 /* TODO Remove this workaround when ViewFlipper is fixed to listen
|
|
253 * to view removals. Android Issue 1784
|
|
254 */
|
|
255 final int numChildren = flip.getChildCount();
|
|
256
|
|
257 if (flip.getDisplayedChild() >= numChildren &&
|
|
258 numChildren > 0) {
|
|
259 flip.setDisplayedChild(numChildren - 1);
|
|
260 }
|
|
261
|
|
262 updateEmptyVisible();
|
|
263 }
|
|
264
|
|
265 // If we just closed the last bridge, go back to the previous activity.
|
|
266 if (flip.getChildCount() == 0) {
|
|
267 finish();
|
|
268 }
|
|
269 }
|
|
270 }
|
|
271
|
|
272 protected View findCurrentView(int id) {
|
|
273 View view = flip.getCurrentView();
|
|
274
|
|
275 if (view == null) return null;
|
|
276
|
|
277 return view.findViewById(id);
|
|
278 }
|
|
279
|
|
280 protected PromptHelper getCurrentPromptHelper() {
|
|
281 View view = findCurrentView(R.id.console_flip);
|
|
282
|
|
283 if (!(view instanceof TerminalView)) return null;
|
|
284
|
|
285 return ((TerminalView)view).bridge.promptHelper;
|
|
286 }
|
|
287
|
|
288 protected void hideAllPrompts() {
|
|
289 stringPromptGroup.setVisibility(View.GONE);
|
|
290 booleanPromptGroup.setVisibility(View.GONE);
|
|
291 // adjust window back if size was changed during prompt input
|
|
292 View view = findCurrentView(R.id.console_flip);
|
|
293
|
|
294 if (!(view instanceof TerminalView)) return;
|
|
295
|
|
296 ((TerminalView)view).bridge.parentChanged((TerminalView)view);
|
|
297 }
|
|
298
|
|
299 private void showEmulatedKeys() {
|
|
300 keyboardGroup.startAnimation(keyboard_fade_in);
|
|
301 keyboardGroup.setVisibility(View.VISIBLE);
|
|
302 actionBar.show();
|
|
303
|
|
304 if (keyboardGroupHider != null)
|
|
305 handler.removeCallbacks(keyboardGroupHider);
|
|
306
|
|
307 keyboardGroupHider = new Runnable() {
|
|
308 public void run() {
|
|
309 if (keyboardGroup.getVisibility() == View.GONE || inActionBarMenu)
|
|
310 return;
|
|
311
|
|
312 keyboardGroup.startAnimation(keyboard_fade_out);
|
|
313 keyboardGroup.setVisibility(View.GONE);
|
|
314 actionBar.hide();
|
|
315 keyboardGroupHider = null;
|
|
316 }
|
|
317 };
|
|
318 handler.postDelayed(keyboardGroupHider, KEYBOARD_DISPLAY_TIME);
|
|
319 }
|
|
320
|
|
321 private void hideEmulatedKeys() {
|
|
322 if (keyboardGroupHider != null)
|
|
323 handler.removeCallbacks(keyboardGroupHider);
|
|
324
|
|
325 keyboardGroup.setVisibility(View.GONE);
|
|
326 actionBar.hide();
|
|
327 }
|
|
328
|
|
329 // more like configureLaxMode -- enable network IO on UI thread
|
|
330 private void configureStrictMode() {
|
|
331 try {
|
|
332 Class.forName("android.os.StrictMode");
|
|
333 StrictModeSetup.run();
|
|
334 }
|
|
335 catch (ClassNotFoundException e) {
|
|
336 }
|
|
337 }
|
|
338 @Override
|
|
339 public void onCreate(Bundle icicle) {
|
|
340 super.onCreate(icicle);
|
|
341 configureStrictMode();
|
|
342 hardKeyboard = getResources().getConfiguration().keyboard ==
|
|
343 Configuration.KEYBOARD_QWERTY;
|
|
344 this.setContentView(R.layout.act_console);
|
|
345 clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
|
|
346 prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
|
347 // TODO find proper way to disable volume key beep if it exists.
|
|
348 setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
|
349 // handle requested console from incoming intent
|
|
350 requested = getIntent().getData();
|
|
351 inflater = LayoutInflater.from(this);
|
|
352 flip = (ViewFlipper)findViewById(R.id.console_flip);
|
|
353 empty = (TextView)findViewById(android.R.id.empty);
|
|
354 stringPromptGroup = (RelativeLayout) findViewById(R.id.console_password_group);
|
|
355 stringPromptInstructions = (TextView) findViewById(R.id.console_password_instructions);
|
|
356 stringPrompt = (EditText)findViewById(R.id.console_password);
|
|
357 stringPrompt.setOnKeyListener(new OnKeyListener() {
|
|
358 public boolean onKey(View v, int keyCode, KeyEvent event) {
|
|
359 if (event.getAction() == KeyEvent.ACTION_UP) return false;
|
|
360
|
|
361 if (keyCode != KeyEvent.KEYCODE_ENTER) return false;
|
|
362
|
|
363 // pass collected password down to current terminal
|
|
364 String value = stringPrompt.getText().toString();
|
|
365 PromptHelper helper = getCurrentPromptHelper();
|
|
366
|
|
367 if (helper == null) return false;
|
|
368
|
|
369 helper.setResponse(value);
|
|
370 // finally clear password for next user
|
|
371 stringPrompt.setText("");
|
|
372 updatePromptVisible();
|
|
373 return true;
|
|
374 }
|
|
375 });
|
|
376 booleanPromptGroup = (RelativeLayout) findViewById(R.id.console_boolean_group);
|
|
377 booleanPrompt = (TextView)findViewById(R.id.console_prompt);
|
|
378 booleanYes = (Button)findViewById(R.id.console_prompt_yes);
|
|
379 booleanYes.setOnClickListener(new OnClickListener() {
|
|
380 public void onClick(View v) {
|
|
381 PromptHelper helper = getCurrentPromptHelper();
|
|
382
|
|
383 if (helper == null) return;
|
|
384
|
|
385 helper.setResponse(Boolean.TRUE);
|
|
386 updatePromptVisible();
|
|
387 }
|
|
388 });
|
|
389 booleanNo = (Button)findViewById(R.id.console_prompt_no);
|
|
390 booleanNo.setOnClickListener(new OnClickListener() {
|
|
391 public void onClick(View v) {
|
|
392 PromptHelper helper = getCurrentPromptHelper();
|
|
393
|
|
394 if (helper == null) return;
|
|
395
|
|
396 helper.setResponse(Boolean.FALSE);
|
|
397 updatePromptVisible();
|
|
398 }
|
|
399 });
|
|
400 // preload animations for terminal switching
|
|
401 slide_left_in = AnimationUtils.loadAnimation(this, R.anim.slide_left_in);
|
|
402 slide_left_out = AnimationUtils.loadAnimation(this, R.anim.slide_left_out);
|
|
403 slide_right_in = AnimationUtils.loadAnimation(this, R.anim.slide_right_in);
|
|
404 slide_right_out = AnimationUtils.loadAnimation(this, R.anim.slide_right_out);
|
|
405 fade_out_delayed = AnimationUtils.loadAnimation(this, R.anim.fade_out_delayed);
|
|
406 fade_stay_hidden = AnimationUtils.loadAnimation(this, R.anim.fade_stay_hidden);
|
|
407 // Preload animation for keyboard button
|
|
408 keyboard_fade_in = AnimationUtils.loadAnimation(this, R.anim.keyboard_fade_in);
|
|
409 keyboard_fade_out = AnimationUtils.loadAnimation(this, R.anim.keyboard_fade_out);
|
|
410 inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
411 keyboardGroup = (RelativeLayout) findViewById(R.id.keyboard_group);
|
|
412 mKeyboardButton = (ImageView) findViewById(R.id.button_keyboard);
|
|
413 mKeyboardButton.setOnClickListener(new OnClickListener() {
|
|
414 public void onClick(View view) {
|
|
415 View flip = findCurrentView(R.id.console_flip);
|
|
416
|
|
417 if (flip == null)
|
|
418 return;
|
|
419
|
|
420 inputManager.showSoftInput(flip, InputMethodManager.SHOW_FORCED);
|
|
421 hideEmulatedKeys();
|
|
422 }
|
|
423 });
|
|
424 final ImageView symButton = (ImageView) findViewById(R.id.button_sym);
|
|
425 symButton.setOnClickListener(new OnClickListener() {
|
|
426 public void onClick(View view) {
|
|
427 View flip = findCurrentView(R.id.console_flip);
|
|
428
|
|
429 if (flip == null) return;
|
|
430
|
|
431 TerminalView terminal = (TerminalView)flip;
|
|
432 terminal.bridge.showCharPickerDialog();
|
|
433 keyboardGroup.setVisibility(View.GONE);
|
|
434 }
|
|
435 });
|
|
436 symButton.setOnLongClickListener(new OnLongClickListener() {
|
|
437 public boolean onLongClick(View view) {
|
|
438 View flip = findCurrentView(R.id.console_flip);
|
|
439
|
|
440 if (flip == null) return false;
|
|
441
|
|
442 TerminalView terminal = (TerminalView)flip;
|
|
443 terminal.bridge.showArrowsDialog();
|
|
444 return true;
|
|
445 }
|
|
446 });
|
|
447 final ImageView mInputButton = (ImageView) findViewById(R.id.button_input);
|
|
448 mInputButton.setOnClickListener(new OnClickListener() {
|
|
449 public void onClick(View view) {
|
|
450 View flip = findCurrentView(R.id.console_flip);
|
|
451
|
|
452 if (flip == null) return;
|
|
453
|
|
454 final TerminalView terminal = (TerminalView)flip;
|
|
455 Thread promptThread = new Thread(new Runnable() {
|
|
456 public void run() {
|
|
457 String inj = getCurrentPromptHelper().requestStringPrompt(null, "");
|
|
458 terminal.bridge.injectString(inj);
|
|
459 }
|
|
460 });
|
|
461 promptThread.setName("Prompt");
|
|
462 promptThread.setDaemon(true);
|
|
463 promptThread.start();
|
|
464 keyboardGroup.setVisibility(View.GONE);
|
|
465 }
|
|
466 });
|
|
467 final ImageView ctrlButton = (ImageView) findViewById(R.id.button_ctrl);
|
|
468 ctrlButton.setOnClickListener(new OnClickListener() {
|
|
469 public void onClick(View view) {
|
|
470 View flip = findCurrentView(R.id.console_flip);
|
|
471
|
|
472 if (flip == null) return;
|
|
473
|
|
474 TerminalView terminal = (TerminalView)flip;
|
|
475 TerminalKeyListener handler = terminal.bridge.getKeyHandler();
|
|
476 handler.metaPress(TerminalKeyListener.META_CTRL_ON);
|
|
477 hideEmulatedKeys();
|
|
478 }
|
|
479 });
|
|
480 ctrlButton.setOnLongClickListener(new OnLongClickListener() {
|
|
481 public boolean onLongClick(View view) {
|
|
482 View flip = findCurrentView(R.id.console_flip);
|
|
483
|
|
484 if (flip == null) return false;
|
|
485
|
|
486 TerminalView terminal = (TerminalView)flip;
|
|
487 terminal.bridge.showCtrlDialog();
|
|
488 return true;
|
|
489 }
|
|
490 });
|
|
491 final ImageView escButton = (ImageView) findViewById(R.id.button_esc);
|
|
492 escButton.setOnClickListener(new OnClickListener() {
|
|
493 public void onClick(View view) {
|
|
494 View flip = findCurrentView(R.id.console_flip);
|
|
495
|
|
496 if (flip == null) return;
|
|
497
|
|
498 TerminalView terminal = (TerminalView)flip;
|
|
499 TerminalKeyListener handler = terminal.bridge.getKeyHandler();
|
|
500 handler.sendEscape();
|
|
501 hideEmulatedKeys();
|
|
502 }
|
|
503 });
|
|
504 escButton.setOnLongClickListener(new OnLongClickListener() {
|
|
505 public boolean onLongClick(View view) {
|
|
506 View flip = findCurrentView(R.id.console_flip);
|
|
507
|
|
508 if (flip == null) return false;
|
|
509
|
|
510 TerminalView terminal = (TerminalView)flip;
|
|
511 terminal.bridge.showFKeysDialog();
|
|
512 return true;
|
|
513 }
|
|
514 });
|
|
515 actionBar = ActionBarWrapper.getActionBar(this);
|
|
516 actionBar.setDisplayHomeAsUpEnabled(true);
|
|
517 actionBar.hide();
|
|
518 actionBar.addOnMenuVisibilityListener(new ActionBarWrapper.OnMenuVisibilityListener() {
|
|
519 public void onMenuVisibilityChanged(boolean isVisible) {
|
|
520 inActionBarMenu = isVisible;
|
|
521
|
|
522 if (isVisible == false) {
|
|
523 hideEmulatedKeys();
|
|
524 }
|
|
525 }
|
|
526 });
|
|
527 // detect fling gestures to switch between terminals
|
|
528 final GestureDetector gestDetect = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
|
|
529 private float totalY = 0;
|
|
530 @Override
|
|
531 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
|
532 final float distx = e2.getRawX() - e1.getRawX();
|
|
533 final float disty = e2.getRawY() - e1.getRawY();
|
|
534 final int goalwidth = flip.getWidth() / 2;
|
|
535
|
|
536 // need to slide across half of display to trigger console change
|
|
537 // make sure user kept a steady hand horizontally
|
|
538 if (Math.abs(disty) < (flip.getHeight() / 4)) {
|
|
539 if (distx > goalwidth) {
|
|
540 shiftCurrentTerminal(SHIFT_RIGHT);
|
|
541 return true;
|
|
542 }
|
|
543
|
|
544 if (distx < -goalwidth) {
|
|
545 shiftCurrentTerminal(SHIFT_LEFT);
|
|
546 return true;
|
|
547 }
|
|
548 }
|
|
549
|
|
550 return false;
|
|
551 }
|
|
552 @Override
|
|
553 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
|
554 // if copying, then ignore
|
|
555 if (copySource != null && copySource.isSelectingForCopy())
|
|
556 return false;
|
|
557
|
|
558 if (e1 == null || e2 == null)
|
|
559 return false;
|
|
560
|
|
561 // if releasing then reset total scroll
|
|
562 if (e2.getAction() == MotionEvent.ACTION_UP) {
|
|
563 totalY = 0;
|
|
564 }
|
|
565
|
|
566 // activate consider if within x tolerance
|
|
567 if (Math.abs(e1.getX() - e2.getX()) < ViewConfiguration.getTouchSlop() * 4) {
|
|
568 View flip = findCurrentView(R.id.console_flip);
|
|
569
|
|
570 if (flip == null) return false;
|
|
571
|
|
572 TerminalView terminal = (TerminalView)flip;
|
|
573 // estimate how many rows we have scrolled through
|
|
574 // accumulate distance that doesn't trigger immediate scroll
|
|
575 totalY += distanceY;
|
|
576 final int moved = (int)(totalY / terminal.bridge.charHeight);
|
|
577
|
|
578 // consume as scrollback only if towards right half of screen
|
|
579 if (e2.getX() > flip.getWidth() / 2) {
|
|
580 if (moved != 0) {
|
|
581 int base = terminal.bridge.buffer.getWindowBase();
|
|
582 terminal.bridge.buffer.setWindowBase(base + moved);
|
|
583 totalY = 0;
|
|
584 return true;
|
|
585 }
|
|
586 }
|
|
587 else {
|
|
588 // otherwise consume as pgup/pgdown for every 5 lines
|
|
589 if (moved > 5) {
|
|
590 ((vt320)terminal.bridge.buffer).keyPressed(vt320.KEY_PAGE_DOWN, ' ', 0);
|
|
591 terminal.bridge.tryKeyVibrate();
|
|
592 totalY = 0;
|
|
593 return true;
|
|
594 }
|
|
595 else if (moved < -5) {
|
|
596 ((vt320)terminal.bridge.buffer).keyPressed(vt320.KEY_PAGE_UP, ' ', 0);
|
|
597 terminal.bridge.tryKeyVibrate();
|
|
598 totalY = 0;
|
|
599 return true;
|
|
600 }
|
|
601 }
|
|
602 }
|
|
603
|
|
604 return false;
|
|
605 }
|
|
606 /*
|
|
607 * Enables longpress and popups menu
|
|
608 *
|
|
609 * @see
|
|
610 * android.view.GestureDetector.SimpleOnGestureListener#
|
|
611 * onLongPress(android.view.MotionEvent)
|
|
612 *
|
|
613 * @return void
|
|
614 */
|
|
615 @Override
|
|
616 public void onLongPress(MotionEvent e) {
|
|
617 List<String> itemList = new ArrayList<String>();
|
|
618 final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
|
|
619
|
|
620 if (terminalView == null)
|
|
621 return;
|
|
622
|
|
623 final TerminalBridge bridge = terminalView.bridge;
|
|
624
|
|
625 if (fullScreen == FULLSCREEN_ON)
|
|
626 itemList.add(ConsoleActivity.this
|
|
627 .getResources().getString(R.string.longpress_disable_full_screen_mode));
|
|
628 else
|
|
629 itemList.add(ConsoleActivity.this
|
|
630 .getResources().getString(R.string.longpress_enable_full_screen_mode));
|
|
631
|
|
632 itemList.add(ConsoleActivity.this
|
|
633 .getResources().getString(R.string.longpress_change_font_size));
|
|
634
|
|
635 if (prefs.getBoolean(PreferenceConstants.EXTENDED_LONGPRESS, false)) {
|
|
636 itemList.add(ConsoleActivity.this
|
|
637 .getResources().getString(R.string.longpress_arrows_dialog));
|
|
638 itemList.add(ConsoleActivity.this
|
|
639 .getResources().getString(R.string.longpress_fkeys_dialog));
|
|
640 itemList.add(ConsoleActivity.this
|
|
641 .getResources().getString(R.string.longpress_ctrl_dialog));
|
|
642 itemList.add(ConsoleActivity.this
|
|
643 .getResources().getString(R.string.longpress_sym_dialog));
|
|
644 }
|
|
645
|
|
646 if (itemList.size() > 0) {
|
|
647 AlertDialog.Builder builder = new AlertDialog.Builder(ConsoleActivity.this);
|
|
648 builder.setTitle(R.string.longpress_select_action);
|
|
649 builder.setItems(itemList.toArray(new CharSequence[itemList.size()]),
|
|
650 new DialogInterface.OnClickListener() {
|
|
651 public void onClick(DialogInterface dialog, int item) {
|
|
652 switch (item) {
|
|
653 case 0:
|
|
654 if (fullScreen == FULLSCREEN_ON) {
|
|
655 setFullScreen(FULLSCREEN_OFF);
|
|
656 }
|
|
657 else
|
|
658 setFullScreen(FULLSCREEN_ON);
|
|
659
|
|
660 break;
|
|
661
|
|
662 case 1:
|
|
663 bridge.showFontSizeDialog();
|
|
664 break;
|
|
665
|
|
666 case 2:
|
|
667 bridge.showArrowsDialog();
|
|
668 break;
|
|
669
|
|
670 case 3:
|
|
671 bridge.showFKeysDialog();
|
|
672 break;
|
|
673
|
|
674 case 4:
|
|
675 bridge.showCtrlDialog();
|
|
676 break;
|
|
677
|
|
678 case 5:
|
|
679 bridge.showCharPickerDialog();
|
|
680 }
|
|
681 }
|
|
682 });
|
|
683 AlertDialog alert = builder.create();
|
|
684 alert.show();
|
|
685 }
|
|
686 }
|
|
687 });
|
|
688 flip.setLongClickable(true);
|
|
689 flip.setOnTouchListener(new OnTouchListener() {
|
|
690 public boolean onTouch(View v, MotionEvent event) {
|
|
691 // when copying, highlight the area
|
|
692 if (copySource != null && copySource.isSelectingForCopy()) {
|
|
693 int row = (int)FloatMath.floor(event.getY() / copySource.charHeight);
|
|
694 int col = (int)FloatMath.floor(event.getX() / copySource.charWidth);
|
|
695 SelectionArea area = copySource.getSelectionArea();
|
|
696
|
|
697 switch (event.getAction()) {
|
|
698 case MotionEvent.ACTION_DOWN:
|
|
699
|
|
700 // recording starting area
|
|
701 if (area.isSelectingOrigin()) {
|
|
702 area.setRow(row);
|
|
703 area.setColumn(col);
|
|
704 lastTouchRow = row;
|
|
705 lastTouchCol = col;
|
|
706 copySource.redraw();
|
|
707 }
|
|
708
|
|
709 return true;
|
|
710
|
|
711 case MotionEvent.ACTION_MOVE:
|
|
712
|
|
713 /* ignore when user hasn't moved since last time so
|
|
714 * we can fine-tune with directional pad
|
|
715 */
|
|
716 if (row == lastTouchRow && col == lastTouchCol)
|
|
717 return true;
|
|
718
|
|
719 // if the user moves, start the selection for other corner
|
|
720 area.finishSelectingOrigin();
|
|
721 // update selected area
|
|
722 area.setRow(row);
|
|
723 area.setColumn(col);
|
|
724 lastTouchRow = row;
|
|
725 lastTouchCol = col;
|
|
726 copySource.redraw();
|
|
727 return true;
|
|
728
|
|
729 case MotionEvent.ACTION_UP:
|
|
730
|
|
731 /* If they didn't move their finger, maybe they meant to
|
|
732 * select the rest of the text with the directional pad.
|
|
733 */
|
|
734 if (area.getLeft() == area.getRight() &&
|
|
735 area.getTop() == area.getBottom()) {
|
|
736 return true;
|
|
737 }
|
|
738
|
|
739 // copy selected area to clipboard
|
|
740 String copiedText = area.copyFrom(copySource.buffer);
|
|
741 clipboard.setText(copiedText);
|
|
742 Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_done, copiedText.length()), Toast.LENGTH_LONG).show();
|
|
743
|
|
744 // fall through to clear state
|
|
745 case MotionEvent.ACTION_CANCEL:
|
|
746 // make sure we clear any highlighted area
|
|
747 area.reset();
|
|
748 copySource.setSelectingForCopy(false);
|
|
749 copySource.redraw();
|
|
750 return true;
|
|
751 }
|
|
752 }
|
|
753
|
|
754 Configuration config = getResources().getConfiguration();
|
|
755
|
|
756 if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
|
757 lastX = event.getX();
|
|
758 lastY = event.getY();
|
|
759 }
|
|
760 else if (event.getAction() == MotionEvent.ACTION_UP
|
|
761 && keyboardGroup.getVisibility() == View.GONE
|
|
762 && event.getEventTime() - event.getDownTime() < CLICK_TIME
|
|
763 && Math.abs(event.getX() - lastX) < MAX_CLICK_DISTANCE
|
|
764 && Math.abs(event.getY() - lastY) < MAX_CLICK_DISTANCE) {
|
|
765 showEmulatedKeys();
|
|
766 }
|
|
767
|
|
768 // pass any touch events back to detector
|
|
769 TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
|
|
770 return terminalView.mScaleDetector.onTouchEvent(event) | gestDetect.onTouchEvent(event);
|
|
771 }
|
|
772 });
|
|
773 }
|
|
774
|
|
775 /**
|
|
776 *
|
|
777 */
|
|
778 private void configureOrientation() {
|
|
779 String rotateDefault;
|
|
780
|
|
781 if (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_NOKEYS)
|
|
782 rotateDefault = PreferenceConstants.ROTATION_PORTRAIT;
|
|
783 else
|
|
784 rotateDefault = PreferenceConstants.ROTATION_LANDSCAPE;
|
|
785
|
|
786 String rotate = prefs.getString(PreferenceConstants.ROTATION, rotateDefault);
|
|
787
|
|
788 if (PreferenceConstants.ROTATION_DEFAULT.equals(rotate))
|
|
789 rotate = rotateDefault;
|
|
790
|
|
791 // request a forced orientation if requested by user
|
|
792 if (PreferenceConstants.ROTATION_LANDSCAPE.equals(rotate)) {
|
|
793 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
|
794 forcedOrientation = true;
|
|
795 }
|
|
796 else if (PreferenceConstants.ROTATION_PORTRAIT.equals(rotate)) {
|
|
797 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
|
798 forcedOrientation = true;
|
|
799 }
|
|
800 else {
|
|
801 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
|
802 forcedOrientation = false;
|
|
803 }
|
|
804 }
|
|
805
|
|
806
|
|
807 @Override
|
|
808 public boolean onCreateOptionsMenu(Menu menu) {
|
|
809 super.onCreateOptionsMenu(menu);
|
|
810 View view = findCurrentView(R.id.console_flip);
|
|
811 final boolean activeTerminal = (view instanceof TerminalView);
|
|
812 boolean sessionOpen = false;
|
|
813 boolean disconnected = false;
|
|
814 boolean canForwardPorts = false;
|
|
815 boolean canTransferFiles = false;
|
|
816
|
|
817 if (activeTerminal) {
|
|
818 TerminalBridge bridge = ((TerminalView) view).bridge;
|
|
819 sessionOpen = bridge.isSessionOpen();
|
|
820 disconnected = bridge.isDisconnected();
|
|
821 canForwardPorts = bridge.canFowardPorts();
|
|
822 canTransferFiles = bridge.canTransferFiles();
|
|
823 }
|
|
824
|
|
825 menu.setQwertyMode(true);
|
|
826
|
|
827 if (!PreferenceConstants.PRE_HONEYCOMB) {
|
|
828 MenuItem ctrlKey = menu.add(getString(R.string.fullscreen));
|
|
829 ctrlKey.setEnabled(activeTerminal);
|
|
830 ctrlKey.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
|
831 ctrlKey.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
832 public boolean onMenuItemClick(MenuItem menuItem) {
|
|
833 if (fullScreen == FULLSCREEN_ON) {
|
|
834 setFullScreen(FULLSCREEN_OFF);
|
|
835 }
|
|
836 else
|
|
837 setFullScreen(FULLSCREEN_ON);
|
|
838
|
|
839 return true;
|
|
840 }
|
|
841 });
|
|
842 }
|
|
843
|
|
844 disconnect = menu.add(R.string.list_host_disconnect);
|
|
845
|
|
846 if (hardKeyboard)
|
|
847 disconnect.setAlphabeticShortcut('w');
|
|
848
|
|
849 if (!sessionOpen && disconnected)
|
|
850 disconnect.setTitle(R.string.console_menu_close);
|
|
851
|
|
852 disconnect.setEnabled(activeTerminal);
|
|
853 disconnect.setIcon(android.R.drawable.ic_menu_close_clear_cancel);
|
|
854 disconnect.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
855 public boolean onMenuItemClick(MenuItem item) {
|
|
856 // disconnect or close the currently visible session
|
|
857 TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
|
|
858 TerminalBridge bridge = terminalView.bridge;
|
|
859 bridge.dispatchDisconnect(true);
|
|
860 return true;
|
|
861 }
|
|
862 });
|
|
863 copy = menu.add(R.string.console_menu_copy);
|
|
864
|
|
865 if (hardKeyboard)
|
|
866 copy.setAlphabeticShortcut('c');
|
|
867
|
|
868 copy.setIcon(android.R.drawable.ic_menu_set_as);
|
|
869 copy.setEnabled(activeTerminal);
|
|
870 copy.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
871 public boolean onMenuItemClick(MenuItem item) {
|
|
872 // mark as copying and reset any previous bounds
|
|
873 TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
|
|
874 copySource = terminalView.bridge;
|
|
875 SelectionArea area = copySource.getSelectionArea();
|
|
876 area.reset();
|
|
877 area.setBounds(copySource.buffer.getColumns(), copySource.buffer.getRows());
|
|
878 copySource.setSelectingForCopy(true);
|
|
879 // Make sure we show the initial selection
|
|
880 copySource.redraw();
|
|
881 Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_start), Toast.LENGTH_LONG).show();
|
|
882 return true;
|
|
883 }
|
|
884 });
|
|
885 paste = menu.add(R.string.console_menu_paste);
|
|
886
|
|
887 if (hardKeyboard)
|
|
888 paste.setAlphabeticShortcut('v');
|
|
889
|
|
890 paste.setIcon(android.R.drawable.ic_menu_edit);
|
|
891 paste.setEnabled(clipboard.hasText() && sessionOpen);
|
|
892 paste.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
893 public boolean onMenuItemClick(MenuItem item) {
|
|
894 // force insert of clipboard text into current console
|
|
895 TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
|
|
896 TerminalBridge bridge = terminalView.bridge;
|
|
897 // pull string from clipboard and generate all events to force down
|
|
898 String clip = clipboard.getText().toString();
|
|
899 bridge.injectString(clip);
|
|
900 return true;
|
|
901 }
|
|
902 });
|
|
903 resize = menu.add(R.string.console_menu_resize);
|
|
904
|
|
905 if (hardKeyboard)
|
|
906 resize.setAlphabeticShortcut('r');
|
|
907
|
|
908 resize.setIcon(android.R.drawable.ic_menu_crop);
|
|
909 resize.setEnabled(sessionOpen);
|
|
910 resize.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
911 public boolean onMenuItemClick(MenuItem item) {
|
|
912 final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
|
|
913 final View resizeView = inflater.inflate(R.layout.dia_resize, null, false);
|
|
914 ((EditText) resizeView.findViewById(R.id.width))
|
|
915 .setText(prefs.getString(PreferenceConstants.DEFAULT_FONT_SIZE_WIDTH, "80"));
|
|
916 ((EditText) resizeView.findViewById(R.id.height))
|
|
917 .setText(prefs.getString(PreferenceConstants.DEFAULT_FONT_SIZE_HEIGHT, "25"));
|
|
918 new AlertDialog.Builder(ConsoleActivity.this)
|
|
919 .setView(resizeView)
|
|
920 .setPositiveButton(R.string.button_resize, new DialogInterface.OnClickListener() {
|
|
921 public void onClick(DialogInterface dialog, int which) {
|
|
922 int width, height;
|
|
923
|
|
924 try {
|
|
925 width = Integer.parseInt(((EditText) resizeView
|
|
926 .findViewById(R.id.width))
|
|
927 .getText().toString());
|
|
928 height = Integer.parseInt(((EditText) resizeView
|
|
929 .findViewById(R.id.height))
|
|
930 .getText().toString());
|
|
931 }
|
|
932 catch (NumberFormatException nfe) {
|
|
933 // TODO change this to a real dialog where we can
|
|
934 // make the input boxes turn red to indicate an error.
|
|
935 return;
|
|
936 }
|
|
937
|
|
938 if (width > 0 && height > 0) {
|
|
939 terminalView.forceSize(width, height);
|
|
940 }
|
|
941 else {
|
|
942 new AlertDialog.Builder(ConsoleActivity.this)
|
|
943 .setTitle(R.string.resize_error_title)
|
|
944 .setMessage(R.string.resize_error_width_height)
|
|
945 .setNegativeButton(R.string.button_close, null)
|
|
946 .show();
|
|
947 }
|
|
948 }
|
|
949 }).setNeutralButton(R.string.button_resize_reset, new DialogInterface.OnClickListener() {
|
|
950 public void onClick(DialogInterface dialog, int which) {
|
|
951 terminalView.bridge.resetSize(terminalView);
|
|
952 }
|
|
953 }).setNegativeButton(android.R.string.cancel, null)
|
|
954 .create().show();
|
|
955 return true;
|
|
956 }
|
|
957 });
|
|
958 screenCapture = menu.add(R.string.console_menu_screencapture);
|
|
959
|
|
960 if (hardKeyboard)
|
|
961 screenCapture.setAlphabeticShortcut('s');
|
|
962
|
|
963 screenCapture.setIcon(android.R.drawable.ic_menu_camera);
|
|
964 screenCapture.setEnabled(activeTerminal);
|
|
965 screenCapture.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
966 public boolean onMenuItemClick(MenuItem item) {
|
|
967 final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
|
|
968 terminalView.bridge.captureScreen();
|
|
969 return true;
|
|
970 }
|
|
971 });
|
|
972 portForward = menu.add(R.string.console_menu_portforwards);
|
|
973
|
|
974 if (hardKeyboard)
|
|
975 portForward.setAlphabeticShortcut('f');
|
|
976
|
|
977 portForward.setIcon(android.R.drawable.ic_menu_manage);
|
|
978 portForward.setEnabled(sessionOpen && canForwardPorts);
|
|
979 portForward.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
980 public boolean onMenuItemClick(MenuItem item) {
|
|
981 TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
|
|
982 TerminalBridge bridge = terminalView.bridge;
|
|
983 Intent intent = new Intent(ConsoleActivity.this, PortForwardListActivity.class);
|
|
984 intent.putExtra(Intent.EXTRA_TITLE, bridge.host.getId());
|
|
985 ConsoleActivity.this.startActivityForResult(intent, REQUEST_EDIT);
|
|
986 return true;
|
|
987 }
|
|
988 });
|
|
989 urlscan = menu.add(R.string.console_menu_urlscan);
|
|
990
|
|
991 if (hardKeyboard)
|
|
992 urlscan.setAlphabeticShortcut('l');
|
|
993
|
|
994 urlscan.setIcon(android.R.drawable.ic_menu_search);
|
|
995 urlscan.setEnabled(activeTerminal);
|
|
996 urlscan.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
997 public boolean onMenuItemClick(MenuItem item) {
|
|
998 View flip = findCurrentView(R.id.console_flip);
|
|
999
|
|
1000 if (flip == null) return true;
|
|
1001
|
|
1002 TerminalView terminal = (TerminalView)flip;
|
|
1003 TerminalKeyListener handler = terminal.bridge.getKeyHandler();
|
|
1004 handler.urlScan(terminal);
|
|
1005 return true;
|
|
1006 }
|
|
1007 });
|
|
1008 download = menu.add(R.string.console_menu_download);
|
|
1009 download.setAlphabeticShortcut('d');
|
|
1010 download.setEnabled(sessionOpen && canTransferFiles);
|
|
1011 download.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
1012 public boolean onMenuItemClick(MenuItem item) {
|
|
1013 final String downloadFolder = prefs.getString(PreferenceConstants.DOWNLOAD_FOLDER, "");
|
|
1014 final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
|
|
1015 final TerminalBridge bridge = terminalView.bridge;
|
|
1016 final EditText textField = new EditText(ConsoleActivity.this);
|
|
1017 new AlertDialog.Builder(ConsoleActivity.this)
|
|
1018 .setTitle(R.string.transfer_select_remote_download_title)
|
|
1019 .setMessage(R.string.transfer_select_remote_download_desc)
|
|
1020 .setView(textField)
|
|
1021 .setPositiveButton(R.string.transfer_button_download, new DialogInterface.OnClickListener() {
|
|
1022 public void onClick(DialogInterface dialog, int whichButton) {
|
|
1023 TransferThread transfer = new TransferThread(ConsoleActivity.this, handler);
|
|
1024
|
|
1025 if (!prefs.getBoolean(PreferenceConstants.BACKGROUND_FILE_TRANSFER, true))
|
|
1026 transfer.setProgressDialogMessage(getString(R.string.transfer_downloading));
|
|
1027
|
|
1028 transfer.download(bridge, textField.getText().toString(), null, downloadFolder);
|
|
1029 }
|
|
1030 }).setNegativeButton(android.R.string.cancel, null).create().show();
|
|
1031 return true;
|
|
1032 }
|
|
1033 });
|
|
1034 upload = menu.add(R.string.console_menu_upload);
|
|
1035 upload.setAlphabeticShortcut('u');
|
|
1036 upload.setEnabled(sessionOpen && canTransferFiles);
|
|
1037 upload.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
1038 public boolean onMenuItemClick(MenuItem item) {
|
|
1039 FileChooser.selectFile(ConsoleActivity.this, ConsoleActivity.this,
|
|
1040 FileChooser.REQUEST_CODE_SELECT_FILE,
|
|
1041 getString(R.string.file_chooser_select_file, getString(R.string.select_for_upload)));
|
|
1042 return true;
|
|
1043 }
|
|
1044 });
|
|
1045 return true;
|
|
1046 }
|
|
1047
|
|
1048 @Override
|
|
1049 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
|
1050 super.onActivityResult(requestCode, resultCode, intent);
|
|
1051
|
|
1052 switch (requestCode) {
|
|
1053 case FileChooser.REQUEST_CODE_SELECT_FILE:
|
|
1054 if (resultCode == RESULT_OK && intent != null) {
|
|
1055 Uri uri = intent.getData();
|
|
1056
|
|
1057 try {
|
|
1058 if (uri != null) {
|
|
1059 fileSelected(new File(URI.create(uri.toString())));
|
|
1060 }
|
|
1061 else {
|
|
1062 String filename = intent.getDataString();
|
|
1063
|
|
1064 if (filename != null) {
|
|
1065 fileSelected(new File(URI.create(filename)));
|
|
1066 }
|
|
1067 }
|
|
1068 }
|
|
1069 catch (IllegalArgumentException e) {
|
|
1070 Log.e(TAG, "Couldn't read from selected file", e);
|
|
1071 }
|
|
1072 }
|
|
1073
|
|
1074 break;
|
|
1075 }
|
|
1076 }
|
|
1077
|
|
1078 public void fileSelected(final File f) {
|
|
1079 String destFileName;
|
|
1080 String uploadFolder = prefs.getString(PreferenceConstants.REMOTE_UPLOAD_FOLDER, null);
|
|
1081 final TransferThread transfer = new TransferThread(ConsoleActivity.this, handler);
|
|
1082 Log.d(TAG, "File chooser returned " + f);
|
|
1083
|
|
1084 if (uploadFolder == null)
|
|
1085 uploadFolder = "";
|
|
1086
|
|
1087 if (!uploadFolder.equals("") && uploadFolder.charAt(uploadFolder.length() - 1) != '/')
|
|
1088 destFileName = uploadFolder + "/" + f.getName();
|
|
1089 else
|
|
1090 destFileName = uploadFolder + f.getName();
|
|
1091
|
|
1092 if (prefs.getBoolean(PreferenceConstants.UPLOAD_DESTINATION_PROMPT, true)) {
|
|
1093 final EditText fileDest = new EditText(ConsoleActivity.this);
|
|
1094 fileDest.setSingleLine();
|
|
1095 fileDest.setText(destFileName);
|
|
1096 new AlertDialog.Builder(ConsoleActivity.this)
|
|
1097 .setTitle(R.string.transfer_select_remote_upload_dest_title)
|
|
1098 .setMessage(getResources().getString(R.string.transfer_select_remote_upload_dest_desc) + "\n" + f.getPath())
|
|
1099 .setView(fileDest)
|
|
1100 .setPositiveButton(R.string.transfer_button_upload, new DialogInterface.OnClickListener() {
|
|
1101 public void onClick(DialogInterface dialog, int whichButton) {
|
|
1102 if (!prefs.getBoolean(PreferenceConstants.BACKGROUND_FILE_TRANSFER, true))
|
|
1103 transfer.setProgressDialogMessage(getString(R.string.transfer_uploading));
|
|
1104
|
|
1105 TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
|
|
1106 TerminalBridge bridge = terminalView.bridge;
|
|
1107 File uf = new File(fileDest.getText().toString());
|
|
1108 String name = "", parent = "";
|
|
1109
|
|
1110 if (uf.getParent() != null)
|
|
1111 parent = uf.getParent().toString();
|
|
1112
|
|
1113 if (uf.getName() != null)
|
|
1114 name = uf.getName().toString();
|
|
1115
|
|
1116 transfer.upload(bridge, f.toString(), name, parent);
|
|
1117 }
|
|
1118 }).setNegativeButton(android.R.string.cancel, null).create().show();
|
|
1119 }
|
|
1120 else {
|
|
1121 if (!prefs.getBoolean(PreferenceConstants.BACKGROUND_FILE_TRANSFER, true))
|
|
1122 transfer.setProgressDialogMessage(getString(R.string.transfer_uploading));
|
|
1123
|
|
1124 TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
|
|
1125 TerminalBridge bridge = terminalView.bridge;
|
|
1126 transfer.upload(bridge, f.toString(), null, uploadFolder);
|
|
1127 }
|
|
1128 }
|
|
1129
|
|
1130 @Override
|
|
1131 public boolean onPrepareOptionsMenu(Menu menu) {
|
|
1132 super.onPrepareOptionsMenu(menu);
|
|
1133 setVolumeControlStream(AudioManager.STREAM_NOTIFICATION);
|
|
1134 final View view = findCurrentView(R.id.console_flip);
|
|
1135 boolean activeTerminal = (view instanceof TerminalView);
|
|
1136 boolean sessionOpen = false;
|
|
1137 boolean disconnected = false;
|
|
1138 boolean canForwardPorts = false;
|
|
1139 boolean canTransferFiles = false;
|
|
1140
|
|
1141 if (activeTerminal) {
|
|
1142 TerminalBridge bridge = ((TerminalView) view).bridge;
|
|
1143 sessionOpen = bridge.isSessionOpen();
|
|
1144 disconnected = bridge.isDisconnected();
|
|
1145 canForwardPorts = bridge.canFowardPorts();
|
|
1146 canTransferFiles = bridge.canTransferFiles();
|
|
1147 }
|
|
1148
|
|
1149 disconnect.setEnabled(activeTerminal);
|
|
1150
|
|
1151 if (sessionOpen || !disconnected)
|
|
1152 disconnect.setTitle(R.string.list_host_disconnect);
|
|
1153 else
|
|
1154 disconnect.setTitle(R.string.console_menu_close);
|
|
1155
|
|
1156 copy.setEnabled(activeTerminal);
|
|
1157 paste.setEnabled(clipboard.hasText() && sessionOpen);
|
|
1158 portForward.setEnabled(sessionOpen && canForwardPorts);
|
|
1159 urlscan.setEnabled(activeTerminal);
|
|
1160 resize.setEnabled(sessionOpen);
|
|
1161 download.setEnabled(sessionOpen && canTransferFiles);
|
|
1162 upload.setEnabled(sessionOpen && canTransferFiles);
|
|
1163 return true;
|
|
1164 }
|
|
1165
|
|
1166 @Override
|
|
1167 public boolean onOptionsItemSelected(MenuItem item) {
|
|
1168 switch (item.getItemId()) {
|
|
1169 case android.R.id.home:
|
|
1170 Intent intent = new Intent(this, HostListActivity.class);
|
|
1171 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
1172 startActivity(intent);
|
|
1173 return true;
|
|
1174
|
|
1175 default:
|
|
1176 return super.onOptionsItemSelected(item);
|
|
1177 }
|
|
1178 }
|
|
1179
|
|
1180 @Override
|
|
1181 public void onOptionsMenuClosed(Menu menu) {
|
|
1182 super.onOptionsMenuClosed(menu);
|
|
1183 setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
|
1184 }
|
|
1185
|
|
1186 @Override
|
|
1187 public void onStart() {
|
|
1188 super.onStart();
|
|
1189 // connect with manager service to find all bridges
|
|
1190 // when connected it will insert all views
|
|
1191 bindService(new Intent(this, TerminalManager.class), connection, Context.BIND_AUTO_CREATE);
|
|
1192
|
|
1193 if (getResources().getConfiguration().hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
|
|
1194 this.mKeyboardButton.setVisibility(View.GONE);
|
|
1195 }
|
|
1196 }
|
|
1197
|
|
1198 @Override
|
|
1199 public void onPause() {
|
|
1200 super.onPause();
|
|
1201 Log.d(TAG, "onPause called");
|
|
1202
|
|
1203 if (forcedOrientation && bound != null)
|
|
1204 bound.setResizeAllowed(false);
|
|
1205 }
|
|
1206
|
|
1207 @Override
|
|
1208 public void onResume() {
|
|
1209 super.onResume();
|
|
1210 Log.d(TAG, "onResume called");
|
|
1211
|
|
1212 // Make sure we don't let the screen fall asleep.
|
|
1213 // This also keeps the Wi-Fi chipset from disconnecting us.
|
|
1214 if (prefs.getBoolean(PreferenceConstants.KEEP_ALIVE, true)) {
|
|
1215 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
1216 }
|
|
1217 else {
|
|
1218 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
1219 }
|
|
1220
|
|
1221 configureOrientation();
|
|
1222
|
|
1223 if (forcedOrientation && bound != null)
|
|
1224 bound.setResizeAllowed(true);
|
|
1225 }
|
|
1226
|
|
1227 /* (non-Javadoc)
|
|
1228 * @see android.app.Activity#onNewIntent(android.content.Intent)
|
|
1229 */
|
|
1230 @Override
|
|
1231 protected void onNewIntent(Intent intent) {
|
|
1232 super.onNewIntent(intent);
|
|
1233 Log.d(TAG, "onNewIntent called");
|
|
1234 requested = intent.getData();
|
|
1235
|
|
1236 if (requested == null) {
|
|
1237 Log.e(TAG, "Got null intent data in onNewIntent()");
|
|
1238 return;
|
|
1239 }
|
|
1240
|
|
1241 if (bound == null) {
|
|
1242 Log.e(TAG, "We're not bound in onNewIntent()");
|
|
1243 return;
|
|
1244 }
|
|
1245
|
|
1246 TerminalBridge requestedBridge = bound.getConnectedBridge(requested.getFragment());
|
|
1247 int requestedIndex = 0;
|
|
1248
|
|
1249 synchronized (flip) {
|
|
1250 if (requestedBridge == null) {
|
|
1251 // If we didn't find the requested connection, try opening it
|
|
1252 try {
|
|
1253 Log.d(TAG, String.format("We couldnt find an existing bridge with URI=%s (nickname=%s)," +
|
|
1254 "so creating one now", requested.toString(), requested.getFragment()));
|
|
1255 requestedBridge = bound.openConnection(requested);
|
|
1256 }
|
|
1257 catch (Exception e) {
|
|
1258 Log.e(TAG, "Problem while trying to create new requested bridge from URI", e);
|
|
1259 // TODO: We should display an error dialog here.
|
|
1260 return;
|
|
1261 }
|
|
1262
|
|
1263 requestedIndex = addNewTerminalView(requestedBridge);
|
|
1264 }
|
|
1265 else {
|
|
1266 final int flipIndex = getFlipIndex(requestedBridge);
|
|
1267
|
|
1268 if (flipIndex > requestedIndex) {
|
|
1269 requestedIndex = flipIndex;
|
|
1270 }
|
|
1271 }
|
|
1272
|
|
1273 setDisplayedTerminal(requestedIndex);
|
|
1274 }
|
|
1275 }
|
|
1276
|
|
1277 @Override
|
|
1278 public void onStop() {
|
|
1279 super.onStop();
|
|
1280 unbindService(connection);
|
|
1281 }
|
|
1282
|
|
1283 protected void shiftCurrentTerminal(final int direction) {
|
|
1284 View overlay;
|
|
1285
|
|
1286 synchronized (flip) {
|
|
1287 boolean shouldAnimate = flip.getChildCount() > 1;
|
|
1288
|
|
1289 // Only show animation if there is something else to go to.
|
|
1290 if (shouldAnimate) {
|
|
1291 // keep current overlay from popping up again
|
|
1292 overlay = findCurrentView(R.id.terminal_overlay);
|
|
1293
|
|
1294 if (overlay != null)
|
|
1295 overlay.startAnimation(fade_stay_hidden);
|
|
1296
|
|
1297 if (direction == SHIFT_LEFT) {
|
|
1298 flip.setInAnimation(slide_left_in);
|
|
1299 flip.setOutAnimation(slide_left_out);
|
|
1300 flip.showNext();
|
|
1301 }
|
|
1302 else if (direction == SHIFT_RIGHT) {
|
|
1303 flip.setInAnimation(slide_right_in);
|
|
1304 flip.setOutAnimation(slide_right_out);
|
|
1305 flip.showPrevious();
|
|
1306 }
|
|
1307 }
|
|
1308
|
|
1309 updateDefault();
|
|
1310
|
|
1311 if (shouldAnimate) {
|
|
1312 // show overlay on new slide and start fade
|
|
1313 overlay = findCurrentView(R.id.terminal_overlay);
|
|
1314
|
|
1315 if (overlay != null)
|
|
1316 overlay.startAnimation(fade_out_delayed);
|
|
1317 }
|
|
1318
|
|
1319 updatePromptVisible();
|
|
1320 }
|
|
1321 }
|
|
1322
|
|
1323 /**
|
|
1324 * Save the currently shown {@link TerminalView} as the default. This is
|
|
1325 * saved back down into {@link TerminalManager} where we can read it again
|
|
1326 * later.
|
|
1327 */
|
|
1328 private void updateDefault() {
|
|
1329 // update the current default terminal
|
|
1330 View view = findCurrentView(R.id.console_flip);
|
|
1331
|
|
1332 if (!(view instanceof TerminalView)) return;
|
|
1333
|
|
1334 TerminalView terminal = (TerminalView)view;
|
|
1335
|
|
1336 if (bound != null) bound.defaultBridge = terminal.bridge;
|
|
1337
|
|
1338 // tell the bridge monitor it has the topmost visible window now.
|
|
1339 if (terminal.bridge.monitor != null) terminal.bridge.monitor.activate();
|
|
1340 }
|
|
1341
|
|
1342 protected void updateEmptyVisible() {
|
|
1343 // update visibility of empty status message
|
|
1344 empty.setVisibility((flip.getChildCount() == 0) ? View.VISIBLE : View.GONE);
|
|
1345 }
|
|
1346
|
|
1347 /**
|
|
1348 * Show any prompts requested by the currently visible {@link TerminalView}.
|
|
1349 */
|
|
1350 protected void updatePromptVisible() {
|
|
1351 // check if our currently-visible terminalbridge is requesting any prompt services
|
|
1352 View view = findCurrentView(R.id.console_flip);
|
|
1353 // Hide all the prompts in case a prompt request was canceled
|
|
1354 hideAllPrompts();
|
|
1355
|
|
1356 if (!(view instanceof TerminalView)) {
|
|
1357 // we dont have an active view, so hide any prompts
|
|
1358 return;
|
|
1359 }
|
|
1360
|
|
1361 PromptHelper prompt = ((TerminalView)view).bridge.promptHelper;
|
|
1362
|
|
1363 if (String.class.equals(prompt.promptRequested)) {
|
|
1364 stringPromptGroup.setVisibility(View.VISIBLE);
|
|
1365 String instructions = prompt.promptInstructions;
|
|
1366 boolean password = prompt.passwordRequested;
|
|
1367
|
|
1368 if (instructions != null && instructions.length() > 0) {
|
|
1369 stringPromptInstructions.setVisibility(View.VISIBLE);
|
|
1370 stringPromptInstructions.setText(instructions);
|
|
1371 }
|
|
1372 else
|
|
1373 stringPromptInstructions.setVisibility(View.GONE);
|
|
1374
|
|
1375 if (password) {
|
|
1376 stringPrompt.setInputType(InputType.TYPE_CLASS_TEXT |
|
|
1377 InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
|
1378 stringPrompt.setTransformationMethod(PasswordTransformationMethod.getInstance());
|
|
1379 }
|
|
1380 else {
|
|
1381 stringPrompt.setInputType(InputType.TYPE_CLASS_TEXT |
|
|
1382 InputType.TYPE_TEXT_FLAG_AUTO_CORRECT);
|
|
1383 stringPrompt.setTransformationMethod(SingleLineTransformationMethod.getInstance());
|
|
1384 }
|
|
1385
|
|
1386 stringPrompt.setText("");
|
|
1387 stringPrompt.setHint(prompt.promptHint);
|
|
1388 stringPrompt.requestFocus();
|
|
1389 }
|
|
1390 else if (Boolean.class.equals(prompt.promptRequested)) {
|
|
1391 booleanPromptGroup.setVisibility(View.VISIBLE);
|
|
1392 booleanPrompt.setText(prompt.promptHint);
|
|
1393 booleanYes.requestFocus();
|
|
1394 }
|
|
1395 else {
|
|
1396 hideAllPrompts();
|
|
1397 view.requestFocus();
|
|
1398 }
|
|
1399 }
|
|
1400
|
|
1401 @Override
|
|
1402 public void onConfigurationChanged(Configuration newConfig) {
|
|
1403 super.onConfigurationChanged(newConfig);
|
|
1404 Log.d(TAG, String.format("onConfigurationChanged; requestedOrientation=%d, newConfig.orientation=%d", getRequestedOrientation(), newConfig.orientation));
|
|
1405
|
|
1406 if (bound != null) {
|
|
1407 if (forcedOrientation &&
|
|
1408 (newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE &&
|
|
1409 getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) ||
|
|
1410 (newConfig.orientation != Configuration.ORIENTATION_PORTRAIT &&
|
|
1411 getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT))
|
|
1412 bound.setResizeAllowed(false);
|
|
1413 else
|
|
1414 bound.setResizeAllowed(true);
|
|
1415
|
|
1416 bound.hardKeyboardHidden = (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES);
|
|
1417 mKeyboardButton.setVisibility(bound.hardKeyboardHidden ? View.VISIBLE : View.GONE);
|
|
1418 }
|
|
1419 }
|
|
1420
|
|
1421 /**
|
|
1422 * Adds a new TerminalBridge to the current set of views in our ViewFlipper.
|
|
1423 *
|
|
1424 * @param bridge TerminalBridge to add to our ViewFlipper
|
|
1425 * @return the child index of the new view in the ViewFlipper
|
|
1426 */
|
|
1427 private int addNewTerminalView(TerminalBridge bridge) {
|
|
1428 // let them know about our prompt handler services
|
|
1429 bridge.promptHelper.setHandler(promptHandler);
|
|
1430 // inflate each terminal view
|
|
1431 RelativeLayout view = (RelativeLayout)inflater.inflate(R.layout.item_terminal, flip, false);
|
|
1432 // set the terminal overlay text
|
|
1433 TextView overlay = (TextView)view.findViewById(R.id.terminal_overlay);
|
|
1434 overlay.setText(bridge.host.getNickname());
|
|
1435 // and add our terminal view control, using index to place behind overlay
|
|
1436 TerminalView terminal = new TerminalView(ConsoleActivity.this, bridge);
|
|
1437 terminal.setId(R.id.console_flip);
|
|
1438 view.addView(terminal, 0);
|
|
1439
|
|
1440 synchronized (flip) {
|
|
1441 // finally attach to the flipper
|
|
1442 flip.addView(view);
|
|
1443 return flip.getChildCount() - 1;
|
|
1444 }
|
|
1445 }
|
|
1446
|
|
1447 private int getFlipIndex(TerminalBridge bridge) {
|
|
1448 synchronized (flip) {
|
|
1449 final int children = flip.getChildCount();
|
|
1450
|
|
1451 for (int i = 0; i < children; i++) {
|
|
1452 final View view = flip.getChildAt(i).findViewById(R.id.console_flip);
|
|
1453
|
|
1454 if (view == null || !(view instanceof TerminalView)) {
|
|
1455 // How did that happen?
|
|
1456 continue;
|
|
1457 }
|
|
1458
|
|
1459 final TerminalView tv = (TerminalView) view;
|
|
1460
|
|
1461 if (tv.bridge == bridge) {
|
|
1462 return i;
|
|
1463 }
|
|
1464 }
|
|
1465 }
|
|
1466
|
|
1467 return -1;
|
|
1468 }
|
|
1469
|
|
1470 /**
|
|
1471 * Displays the child in the ViewFlipper at the requestedIndex and updates the prompts.
|
|
1472 *
|
|
1473 * @param requestedIndex the index of the terminal view to display
|
|
1474 */
|
|
1475 private void setDisplayedTerminal(int requestedIndex) {
|
|
1476 synchronized (flip) {
|
|
1477 try {
|
|
1478 // show the requested bridge if found, also fade out overlay
|
|
1479 flip.setDisplayedChild(requestedIndex);
|
|
1480 flip.getCurrentView().findViewById(R.id.terminal_overlay)
|
|
1481 .startAnimation(fade_out_delayed);
|
|
1482 }
|
|
1483 catch (NullPointerException npe) {
|
|
1484 Log.d(TAG, "View went away when we were about to display it", npe);
|
|
1485 }
|
|
1486
|
|
1487 updatePromptVisible();
|
|
1488 updateEmptyVisible();
|
|
1489 }
|
|
1490 }
|
|
1491
|
|
1492 private void setFullScreen(int fullScreen) {
|
|
1493 if (fullScreen != this.fullScreen) {
|
|
1494 if (fullScreen == FULLSCREEN_ON) {
|
|
1495 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
|
1496 WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
1497 }
|
|
1498 else {
|
|
1499 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
1500 }
|
|
1501
|
|
1502 this.fullScreen = fullScreen;
|
|
1503
|
|
1504 if (bound != null)
|
|
1505 bound.setFullScreen(this.fullScreen);
|
|
1506 }
|
|
1507 }
|
|
1508 }
|