/**
* Copyright (C) 2013- Iordan Iordanov
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
package com.undatech.opaque;
import com.undatech.opaque.proxmox.*;
import com.undatech.opaque.proxmox.pojo.*;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import javax.security.auth.login.LoginException;
import org.apache.http.*;
import org.apache.http.conn.HttpHostConnectException;
import org.json.JSONException;
import com.iiordanov.android.zoomer.ZoomControls;
import com.undatech.opaque.R;
import com.undatech.opaque.dialogs.GetTextFragment;
import com.undatech.opaque.dialogs.SelectTextElementFragment;
import com.undatech.opaque.input.*;
import android.app.Activity;
import android.app.Dialog;
import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.os.Vibrator;
import android.provider.MediaStore;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.inputmethod.InputMethodManager;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.View.OnKeyListener;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.Toast;
public class RemoteCanvasActivity extends FragmentActivity implements OnKeyListener,
SelectTextElementFragment.OnFragmentDismissedListener,
GetTextFragment.OnFragmentDismissedListener {
private final static String TAG = "RemoteCanvasActivity";
public RemoteCanvas canvas;
private InputHandler inputHandler;
Map<Integer, InputHandler> inputHandlerIdMap;
private ConnectionSettings connection;
ZoomControls kbdIcon;
RemoteCanvasActivityHandler handler;
private Vibrator myVibrator;
RelativeLayout layoutKeys;
ImageButton keyStow;
ImageButton keyCtrl;
boolean keyCtrlToggled;
ImageButton keySuper;
boolean keySuperToggled;
ImageButton keyAlt;
boolean keyAltToggled;
ImageButton keyTab;
ImageButton keyEsc;
ImageButton keyShift;
boolean keyShiftToggled;
ImageButton keyUp;
ImageButton keyDown;
ImageButton keyLeft;
ImageButton keyRight;
boolean hardKeyboardExtended;
boolean extraKeysHidden = false;
int prevBottomOffset = 0;
/**
* Enables sticky immersive mode if supported.
*/
private void enableImmersive() {
if (Constants.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
canvas.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
enableImmersive();
}
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
// TODO: Implement left-icon
//requestWindowFeature(Window.FEATURE_LEFT_ICON);
//setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.icon);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.canvas);
canvas = (RemoteCanvas) findViewById(R.id.canvas);
startConnection();
}
private void startConnection() {
Intent i = getIntent();
String vvFileName = retrieveVvFileFromIntent(i);
if (vvFileName == null) {
android.util.Log.d(TAG, "Initializing session from connection settings.");
connection = (ConnectionSettings)i.getSerializableExtra("com.undatech.opaque.ConnectionSettings");
handler = new RemoteCanvasActivityHandler(this, canvas, connection);
if (connection.getConnectionType().equals(getResources().getString(R.string.connection_type_pve))) {
canvas.initializePve(connection, handler);
} else {
canvas.initialize(connection, handler);
}
} else {
android.util.Log.d(TAG, "Initializing session from vv file: " + vvFileName);
File f = new File(vvFileName);
if (!f.exists()) {
// Quit with an error if the file does not exist.
MessageDialogs.displayMessageAndFinish(this, R.string.vv_file_not_found, R.string.error_dialog_title);
return;
}
connection = new ConnectionSettings(Constants.DEFAULT_SETTINGS_FILE);
connection.loadFromSharedPreferences(getApplicationContext());
handler = new RemoteCanvasActivityHandler(this, canvas, connection);
canvas.initialize(vvFileName, connection, handler);
}
// If we still don't have settings after this, we cannot continue.
if (connection == null) {
return;
}
canvas.setOnKeyListener(this);
canvas.setFocusableInTouchMode(true);
canvas.setDrawingCacheEnabled(false);
// If rotation is disabled, fix the orientation to the current one.
if (!connection.isRotationEnabled()) {
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
}
// This code detects when the soft keyboard is up and sets an appropriate visibleHeight in the canvas.
// When the keyboard is gone, it resets visibleHeight and pans zero distance to prevent us from being
// below the desktop image (if we scrolled all the way down when the keyboard was up).
// TODO: Move this into a separate thread, and post the visibility changes to the handler.
// to avoid occupying the UI thread with this.
final View rootView = ((ViewGroup)findViewById(android.R.id.content)).getChildAt(0);
rootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect r = new Rect();
rootView.getWindowVisibleDisplayFrame(r);
// To avoid setting the visible height to a wrong value after an screen unlock event
// (when r.bottom holds the width of the screen rather than the height due to a rotation)
// we make sure r.top is zero (i.e. there is no notification bar and we are in full-screen mode)
// It's a bit of a hack.
if (r.top == 0) {
if (canvas.myDrawable != null) {
canvas.setVisibleDesktopHeight(r.bottom);
canvas.relativePan(0, 0);
}
}
// Enable/show the zoomer if the keyboard is gone, and disable/hide otherwise.
// We detect the keyboard if more than 19% of the screen is covered.
int offset = 0;
int rootViewHeight = rootView.getHeight();
if (r.bottom > rootViewHeight*0.81) {
offset = rootViewHeight - r.bottom;
// Soft Kbd gone, shift the meta keys and arrows down.
if (layoutKeys != null) {
layoutKeys.offsetTopAndBottom(offset);
keyStow.offsetTopAndBottom(offset);
if (prevBottomOffset != offset) {
setExtraKeysVisibility(View.GONE, false);
canvas.invalidate();
kbdIcon.enable();
}
}
} else {
offset = r.bottom - rootViewHeight;
// Soft Kbd up, shift the meta keys and arrows up.
if (layoutKeys != null) {
layoutKeys.offsetTopAndBottom(offset);
keyStow.offsetTopAndBottom(offset);
if (prevBottomOffset != offset) {
setExtraKeysVisibility(View.VISIBLE, true);
canvas.invalidate();
kbdIcon.hide();
kbdIcon.disable();
}
}
}
setKeyStowDrawableAndVisibility();
prevBottomOffset = offset;
enableImmersive();
}
});
kbdIcon = (ZoomControls) findViewById(R.id.zoomer);
kbdIcon.hide();
kbdIcon.setOnZoomKeyboardClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
InputMethodManager inputMgr = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
inputMgr.toggleSoftInput(0, 0);
}
});
kbdIcon.setOnShowMenuClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
RemoteCanvasActivity.this.openOptionsMenu();
}
});
// Initialize and define actions for on-screen keys.
initializeOnScreenKeys ();
myVibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
// Initialize map from XML IDs to input handlers.
inputHandlerIdMap = new HashMap<Integer, InputHandler>();
inputHandlerIdMap.put(R.id.inputMethodDirectSwipePan, new InputHandlerDirectSwipePan(this, canvas, myVibrator));
inputHandlerIdMap.put(R.id.inputMethodDirectDragPan, new InputHandlerDirectDragPan (this, canvas, myVibrator));
inputHandlerIdMap.put(R.id.inputMethodTouchpad, new InputHandlerTouchpad (this, canvas, myVibrator));
inputHandlerIdMap.put(R.id.inputMethodSingleHanded, new InputHandlerSingleHanded (this, canvas, myVibrator));
android.util.Log.e(TAG, "connection.getInputMethod(): " + connection.getInputMethod());
inputHandler = idToInputHandler(connection.getInputMethod());
}
/**
* Gets called when a new intent comes in.
*/
@Override
protected void onNewIntent (Intent i) {
android.util.Log.e(TAG, "onNewIntent called");
canvas.disconnectAndCleanUp();
setIntent(i);
startConnection();
}
/**
* Outputs the given InputStream to a file.
* @param is
* @param file
* @throws IOException
*/
void outputToFile (InputStream is, File file) throws IOException {
BufferedInputStream bis = new BufferedInputStream(is);
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] data = new byte[Constants.URL_BUFFER_SIZE];
int current = 0;
while((current = bis.read(data, 0, data.length)) != -1){
buffer.write(data, 0, current);
}
FileOutputStream fos = new FileOutputStream(file);
fos.write(buffer.toByteArray());
fos.close();
}
/**
* Retrieves a vv file from the intent if possible and returns the path to it.
* @param i
* @return the vv file name or NULL if no file was discovered.
*/
private String retrieveVvFileFromIntent(Intent i) {
final Uri data = i.getData();
String vvFileName = null;
final String tempVvFile = getFilesDir() + "/tempfile.vv";
int msgId = 0;
android.util.Log.d(TAG, "Got intent: " + i.toString());
if (data != null) {
android.util.Log.d(TAG, "Got data: " + data.toString());
final String dataString = data.toString();
if (dataString.startsWith("http")) {
android.util.Log.d(TAG, "Intent is with http scheme.");
msgId = R.string.error_failed_to_download_vv_http;
deleteMyFile(tempVvFile);
// Spin up a thread to grab the file over the network.
Thread t = new Thread () {
@Override
public void run () {
try {
// Download the file and write it out.
URL url = new URL (data.toString());
File file = new File(tempVvFile);
URLConnection ucon = url.openConnection();
outputToFile(ucon.getInputStream(), new File(tempVvFile));
synchronized(RemoteCanvasActivity.this) {
RemoteCanvasActivity.this.notify();
}
} catch (IOException e) {
int what = Constants.VV_OVER_HTTP_FAILURE;
if (dataString.startsWith("https")) {
what = Constants.VV_OVER_HTTPS_FAILURE;
}
// Quit with an error we could not download the .vv file.
handler.sendEmptyMessage(what);
}
}
};
t.start();
synchronized (this) {
try {
this.wait(Constants.VV_GET_FILE_TIMEOUT);
} catch (InterruptedException e) {
vvFileName = null;
e.printStackTrace();
}
vvFileName = tempVvFile;
}
} else if (dataString.startsWith("file")) {
android.util.Log.d(TAG, "Intent is with file scheme.");
msgId = R.string.error_failed_to_obtain_vv_file;
vvFileName = data.getPath();
} else if (dataString.startsWith("content")) {
android.util.Log.d(TAG, "Intent is with content scheme.");
msgId = R.string.error_failed_to_obtain_vv_content;
deleteMyFile(tempVvFile);
try {
outputToFile(getContentResolver().openInputStream(data), new File(tempVvFile));
vvFileName = tempVvFile;
} catch (IOException e) {
android.util.Log.e(TAG, "Could not write temp file out.");
e.printStackTrace();
}
}
// Check if we were successful in obtaining a file and put up an error dialog if not.
if (dataString.startsWith("http") || dataString.startsWith("file") || dataString.startsWith("content")) {
if (vvFileName == null)
MessageDialogs.displayMessageAndFinish(this, msgId, R.string.error_dialog_title);
}
android.util.Log.d(TAG, "Got filename: " + vvFileName);
}
return vvFileName;
}
private void deleteMyFile (String path) {
new File(path).delete();
}
private void setKeyStowDrawableAndVisibility() {
Drawable replacer = null;
if (layoutKeys.getVisibility() == View.GONE)
replacer = getResources().getDrawable(R.drawable.showkeys);
else
replacer = getResources().getDrawable(R.drawable.hidekeys);
keyStow.setBackgroundDrawable(replacer);
if (connection.getExtraKeysToggleType() == Constants.EXTRA_KEYS_OFF)
keyStow.setVisibility(View.GONE);
else
keyStow.setVisibility(View.VISIBLE);
}
/**
* Initializes the on-screen keys for meta keys and arrow keys.
*/
private void initializeOnScreenKeys () {
layoutKeys = (RelativeLayout) findViewById(R.id.layoutKeys);
keyStow = (ImageButton) findViewById(R.id.keyStow);
setKeyStowDrawableAndVisibility();
keyStow.setOnClickListener(new OnClickListener () {
@Override
public void onClick(View arg0) {
if (layoutKeys.getVisibility() == View.VISIBLE) {
extraKeysHidden = true;
setExtraKeysVisibility(View.GONE, false);
} else {
extraKeysHidden = false;
setExtraKeysVisibility(View.VISIBLE, true);
}
layoutKeys.offsetTopAndBottom(prevBottomOffset);
setKeyStowDrawableAndVisibility();
}
});
// Define action of tab key and meta keys.
keyTab = (ImageButton) findViewById(R.id.keyTab);
keyTab.setOnTouchListener(new OnTouchListener () {
@Override
public boolean onTouch(View arg0, MotionEvent e) {
RemoteKeyboard k = canvas.getKeyboard();
int key = KeyEvent.KEYCODE_TAB;
if (e.getAction() == MotionEvent.ACTION_DOWN) {
myVibrator.vibrate(Constants.SHORT_VIBRATION);
keyTab.setImageResource(R.drawable.tabon);
k.repeatKeyEvent(key, new KeyEvent(e.getAction(), key));
return true;
} else if (e.getAction() == MotionEvent.ACTION_UP) {
keyTab.setImageResource(R.drawable.taboff);
resetOnScreenKeys (0);
k.stopRepeatingKeyEvent();
return true;
}
return false;
}
});
keyEsc = (ImageButton) findViewById(R.id.keyEsc);
keyEsc.setOnTouchListener(new OnTouchListener () {
@Override
public boolean onTouch(View arg0, MotionEvent e) {
RemoteKeyboard k = canvas.getKeyboard();
int key = 111; /* KEYCODE_ESCAPE */
if (e.getAction() == MotionEvent.ACTION_DOWN) {
myVibrator.vibrate(Constants.SHORT_VIBRATION);
keyEsc.setImageResource(R.drawable.escon);
k.repeatKeyEvent(key, new KeyEvent(e.getAction(), key));
return true;
} else if (e.getAction() == MotionEvent.ACTION_UP) {
keyEsc.setImageResource(R.drawable.escoff);
resetOnScreenKeys (0);
k.stopRepeatingKeyEvent();
return true;
}
return false;
}
});
keyCtrl = (ImageButton) findViewById(R.id.keyCtrl);
keyCtrl.setOnClickListener(new OnClickListener () {
@Override
public void onClick(View arg0) {
boolean on = canvas.getKeyboard().onScreenCtrlToggle();
keyCtrlToggled = false;
if (on)
keyCtrl.setImageResource(R.drawable.ctrlon);
else
keyCtrl.setImageResource(R.drawable.ctrloff);
}
});
keyCtrl.setOnLongClickListener(new OnLongClickListener () {
@Override
public boolean onLongClick(View arg0) {
myVibrator.vibrate(Constants.SHORT_VIBRATION);
boolean on = canvas.getKeyboard().onScreenCtrlToggle();
keyCtrlToggled = true;
if (on)
keyCtrl.setImageResource(R.drawable.ctrlon);
else
keyCtrl.setImageResource(R.drawable.ctrloff);
return true;
}
});
keySuper = (ImageButton) findViewById(R.id.keySuper);
keySuper.setOnClickListener(new OnClickListener () {
@Override
public void onClick(View arg0) {
boolean on = canvas.getKeyboard().onScreenSuperToggle();
keySuperToggled = false;
if (on)
keySuper.setImageResource(R.drawable.superon);
else
keySuper.setImageResource(R.drawable.superoff);
}
});
keySuper.setOnLongClickListener(new OnLongClickListener () {
@Override
public boolean onLongClick(View arg0) {
myVibrator.vibrate(Constants.SHORT_VIBRATION);
boolean on = canvas.getKeyboard().onScreenSuperToggle();
keySuperToggled = true;
if (on)
keySuper.setImageResource(R.drawable.superon);
else
keySuper.setImageResource(R.drawable.superoff);
return true;
}
});
keyAlt = (ImageButton) findViewById(R.id.keyAlt);
keyAlt.setOnClickListener(new OnClickListener () {
@Override
public void onClick(View arg0) {
boolean on = canvas.getKeyboard().onScreenAltToggle();
keyAltToggled = false;
if (on)
keyAlt.setImageResource(R.drawable.alton);
else
keyAlt.setImageResource(R.drawable.altoff);
}
});
keyAlt.setOnLongClickListener(new OnLongClickListener () {
@Override
public boolean onLongClick(View arg0) {
myVibrator.vibrate(Constants.SHORT_VIBRATION);
boolean on = canvas.getKeyboard().onScreenAltToggle();
keyAltToggled = true;
if (on)
keyAlt.setImageResource(R.drawable.alton);
else
keyAlt.setImageResource(R.drawable.altoff);
return true;
}
});
keyShift = (ImageButton) findViewById(R.id.keyShift);
keyShift.setOnClickListener(new OnClickListener () {
@Override
public void onClick(View arg0) {
boolean on = canvas.getKeyboard().onScreenShiftToggle();
keyShiftToggled = false;
if (on)
keyShift.setImageResource(R.drawable.shifton);
else
keyShift.setImageResource(R.drawable.shiftoff);
}
});
keyShift.setOnLongClickListener(new OnLongClickListener () {
@Override
public boolean onLongClick(View arg0) {
myVibrator.vibrate(Constants.SHORT_VIBRATION);
boolean on = canvas.getKeyboard().onScreenShiftToggle();
keyShiftToggled = true;
if (on)
keyShift.setImageResource(R.drawable.shifton);
else
keyShift.setImageResource(R.drawable.shiftoff);
return true;
}
});
// Define action of arrow keys.
keyUp = (ImageButton) findViewById(R.id.keyUpArrow);
keyUp.setOnTouchListener(new OnTouchListener () {
@Override
public boolean onTouch(View arg0, MotionEvent e) {
RemoteKeyboard k = canvas.getKeyboard();
int key = KeyEvent.KEYCODE_DPAD_UP;
if (e.getAction() == MotionEvent.ACTION_DOWN) {
myVibrator.vibrate(Constants.SHORT_VIBRATION);
keyUp.setImageResource(R.drawable.upon);
k.repeatKeyEvent(key, new KeyEvent(e.getAction(), key));
return true;
} else if (e.getAction() == MotionEvent.ACTION_UP) {
keyUp.setImageResource(R.drawable.upoff);
resetOnScreenKeys (0);
k.stopRepeatingKeyEvent();
return true;
}
return false;
}
});
keyDown = (ImageButton) findViewById(R.id.keyDownArrow);
keyDown.setOnTouchListener(new OnTouchListener () {
@Override
public boolean onTouch(View arg0, MotionEvent e) {
RemoteKeyboard k = canvas.getKeyboard();
int key = KeyEvent.KEYCODE_DPAD_DOWN;
if (e.getAction() == MotionEvent.ACTION_DOWN) {
myVibrator.vibrate(Constants.SHORT_VIBRATION);
keyDown.setImageResource(R.drawable.downon);
k.repeatKeyEvent(key, new KeyEvent(e.getAction(), key));
return true;
} else if (e.getAction() == MotionEvent.ACTION_UP) {
keyDown.setImageResource(R.drawable.downoff);
resetOnScreenKeys (0);
k.stopRepeatingKeyEvent();
return true;
}
return false;
}
});
keyLeft = (ImageButton) findViewById(R.id.keyLeftArrow);
keyLeft.setOnTouchListener(new OnTouchListener () {
@Override
public boolean onTouch(View arg0, MotionEvent e) {
RemoteKeyboard k = canvas.getKeyboard();
int key = KeyEvent.KEYCODE_DPAD_LEFT;
if (e.getAction() == MotionEvent.ACTION_DOWN) {
myVibrator.vibrate(Constants.SHORT_VIBRATION);
keyLeft.setImageResource(R.drawable.lefton);
k.repeatKeyEvent(key, new KeyEvent(e.getAction(), key));
return true;
} else if (e.getAction() == MotionEvent.ACTION_UP) {
keyLeft.setImageResource(R.drawable.leftoff);
resetOnScreenKeys (0);
k.stopRepeatingKeyEvent();
return true;
}
return false;
}
});
keyRight = (ImageButton) findViewById(R.id.keyRightArrow);
keyRight.setOnTouchListener(new OnTouchListener () {
@Override
public boolean onTouch(View arg0, MotionEvent e) {
RemoteKeyboard k = canvas.getKeyboard();
int key = KeyEvent.KEYCODE_DPAD_RIGHT;
if (e.getAction() == MotionEvent.ACTION_DOWN) {
myVibrator.vibrate(Constants.SHORT_VIBRATION);
keyRight.setImageResource(R.drawable.righton);
k.repeatKeyEvent(key, new KeyEvent(e.getAction(), key));
return true;
} else if (e.getAction() == MotionEvent.ACTION_UP) {
keyRight.setImageResource(R.drawable.rightoff);
resetOnScreenKeys (0);
k.stopRepeatingKeyEvent();
return true;
}
return false;
}
});
}
/**
* Resets the state and image of the on-screen keys.
*/
private void resetOnScreenKeys (int keyCode) {
// Do not reset on-screen keys if keycode is SHIFT.
switch (keyCode) {
case KeyEvent.KEYCODE_SHIFT_LEFT:
case KeyEvent.KEYCODE_SHIFT_RIGHT: return;
}
if (!keyCtrlToggled) {
keyCtrl.setImageResource(R.drawable.ctrloff);
canvas.getKeyboard().onScreenCtrlOff();
}
if (!keyAltToggled) {
keyAlt.setImageResource(R.drawable.altoff);
canvas.getKeyboard().onScreenAltOff();
}
if (!keySuperToggled) {
keySuper.setImageResource(R.drawable.superoff);
canvas.getKeyboard().onScreenSuperOff();
}
if (!keyShiftToggled) {
keyShift.setImageResource(R.drawable.shiftoff);
canvas.getKeyboard().onScreenShiftOff();
}
}
/**
* Sets the visibility of the extra keys appropriately.
*/
private void setExtraKeysVisibility (int visibility, boolean forceVisible) {
Configuration config = getResources().getConfiguration();
boolean makeVisible = forceVisible;
if (config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO)
makeVisible = true;
if (!extraKeysHidden && makeVisible &&
connection.getExtraKeysToggleType() == Constants.EXTRA_KEYS_ON) {
layoutKeys.setVisibility(View.VISIBLE);
layoutKeys.invalidate();
return;
}
if (visibility == View.GONE) {
layoutKeys.setVisibility(View.GONE);
layoutKeys.invalidate();
}
}
/*
* TODO: REMOVE THIS AS SOON AS POSSIBLE.
* onPause: This is an ugly hack for the Playbook, because the Playbook hides the keyboard upon unlock.
* This causes the visible height to remain less, as if the soft keyboard is still up. This hack must go
* away as soon as the Playbook doesn't need it anymore.
*/
@Override
protected void onPause(){
super.onPause();
try {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(canvas.getWindowToken(), 0);
} catch (NullPointerException e) { }
}
/*
* TODO: REMOVE THIS AS SOON AS POSSIBLE.
* onResume: This is an ugly hack for the Playbook which hides the keyboard upon unlock. This causes the visible
* height to remain less, as if the soft keyboard is still up. This hack must go away as soon
* as the Playbook doesn't need it anymore.
*/
@Override
protected void onResume(){
super.onResume();
Log.i(TAG, "onResume called.");
try {
canvas.postInvalidateDelayed(600);
} catch (NullPointerException e) { }
}
ConnectionSettings getConnection() {
return connection;
}
@Override
protected Dialog onCreateDialog(int dialogID) {
switch (dialogID) {
// TODO: Introduce the ability to send text collected from an EditText in the future.
case R.id.menuHelpInputMethod:
return createHelpDialog ();
}
return createHelpDialog ();
}
/**
* Creates the help dialog for this activity.
*/
private Dialog createHelpDialog() {
AlertDialog.Builder adb = new AlertDialog.Builder(this)
.setMessage(R.string.input_method_help_text)
.setPositiveButton(R.string.close,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int whichButton) {
// We don't have to do anything.
}
});
Dialog d = adb.setView(new ListView (this)).create();
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
lp.copyFrom(d.getWindow().getAttributes());
lp.width = WindowManager.LayoutParams.FILL_PARENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
d.show();
d.getWindow().setAttributes(lp);
return d;
}
/**
* This runnable fixes things up after a rotation.
*/
private Runnable rotationCorrector = new Runnable() {
public void run() {
try { correctAfterRotation (); } catch (NullPointerException e) { }
}
};
/**
* This function is called by the rotationCorrector runnable
* to fix things up after a rotation.
*/
private void correctAfterRotation () {
// Its quite common to see NullPointerExceptions here when this function is called
// at the point of disconnection. Hence, we catch and ignore the error.
float oldScale = canvas.canvasZoomer.getZoomFactor();
int x = canvas.absX;
int y = canvas.absY;
canvas.canvasZoomer.resetScaling();
float newScale = canvas.canvasZoomer.getZoomFactor();
canvas.canvasZoomer.changeZoom(oldScale/newScale);
newScale = canvas.canvasZoomer.getZoomFactor();
if (newScale <= oldScale) {
canvas.absX = x;
canvas.absY = y;
canvas.relativePan(0, 0);
}
if (connection.isRequestingNewDisplayResolution()) {
canvas.spicecomm.requestNewResolutionIfNeeded();
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (connection.isRotationEnabled()) {
try {
setExtraKeysVisibility(View.GONE, false);
// Correct a couple of times just in case. There is no visual effect.
handler.postDelayed(rotationCorrector, 600);
handler.postDelayed(rotationCorrector, 1200);
} catch (NullPointerException e) { }
}
}
@Override
protected void onStart() {
super.onStart();
try {
canvas.postInvalidateDelayed(800);
} catch (NullPointerException e) { }
}
@Override
protected void onStop() {
super.onStop();
}
@Override
protected void onRestart() {
super.onRestart();
try {
canvas.postInvalidateDelayed(1000);
} catch (NullPointerException e) { }
}
/**
* Try to find an input handler by the specified string ID.
* @param inputHandlerId
* @return
*/
InputHandler idToInputHandler(String inputHandlerId) {
Iterator<Integer> ids = inputHandlerIdMap.keySet().iterator();
while (ids.hasNext()) {
Integer id = ids.next();
InputHandler h = inputHandlerIdMap.get(id);
if (inputHandlerId.equals(h.getId())) {
return h;
}
}
return null;
}
/**
* If id corresponds to an input handler, return the XML id of the menu item corresponding
* to the input handler, otherwise return -1.
* @param inputHandlerId
* @return
*/
int inputHandlerIdToXmlId (String inputHandlerId) {
Iterator<Integer> ids = inputHandlerIdMap.keySet().iterator();
while (ids.hasNext()) {
Integer id = ids.next();
InputHandler h = inputHandlerIdMap.get(id);
if (inputHandlerId.equals(h.getId())) {
return id;
}
}
return -1;
}
@Override
protected void onDestroy() {
super.onDestroy();
if (canvas != null)
canvas.disconnectAndCleanUp();
canvas = null;
connection = null;
kbdIcon = null;
inputHandler = null;
System.gc();
}
@Override
public boolean onKey(View v, int keyCode, KeyEvent evt) {
boolean consumed = false;
if (keyCode == KeyEvent.KEYCODE_MENU) {
if (evt.getAction() == KeyEvent.ACTION_DOWN)
return super.onKeyDown(keyCode, evt);
else
return super.onKeyUp(keyCode, evt);
}
try {
if (evt.getAction() == KeyEvent.ACTION_DOWN || evt.getAction() == KeyEvent.ACTION_MULTIPLE) {
consumed = inputHandler.onKeyDown(keyCode, evt);
} else if (evt.getAction() == KeyEvent.ACTION_UP){
consumed = inputHandler.onKeyUp(keyCode, evt);
}
resetOnScreenKeys (keyCode);
} catch (NullPointerException e) { }
return consumed;
}
public void displayInputModeInfo(boolean showLonger) {
if (showLonger) {
final Toast t = Toast.makeText(this, inputHandler.getDescription(), Toast.LENGTH_LONG);
TimerTask tt = new TimerTask () {
@Override
public void run() {
t.show();
try { Thread.sleep(2000); } catch (InterruptedException e) { }
t.show();
}};
new Timer ().schedule(tt, 2000);
t.show();
} else {
Toast t = Toast.makeText(this, inputHandler.getDescription(), Toast.LENGTH_SHORT);
t.show();
}
}
// Send touch events or mouse events like button clicks to be handled.
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
return inputHandler.onTouchEvent(event);
} catch (NullPointerException e) { }
return false;
}
// Send e.g. mouse events like hover and scroll to be handled.
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
// Ignore TOOL_TYPE_FINGER events that come from the touchscreen with y == 0.0
// which cause pointer jumping trouble for some users.
int a = event.getAction();
if (! ( (a == MotionEvent.ACTION_HOVER_ENTER ||
a == MotionEvent.ACTION_HOVER_EXIT ||
a == MotionEvent.ACTION_HOVER_MOVE) &&
event.getSource() == InputDevice.SOURCE_TOUCHSCREEN &&
event.getToolType(0) == MotionEvent.TOOL_TYPE_FINGER
) ) {
try {
return inputHandler.onTouchEvent(event);
} catch (NullPointerException e) { }
}
return super.onGenericMotionEvent(event);
}
public float getSensitivity() {
return 2.0f;
}
public boolean getAccelerationEnabled() {
return true;
}
final long hideKbdIconDelay = 2500;
KbdIconHiderRunnable kbdIconHider = new KbdIconHiderRunnable();
public void showKbdIcon() {
kbdIcon.show();
canvas.handler.removeCallbacks(kbdIconHider);
canvas.handler.postAtTime(kbdIconHider, SystemClock.uptimeMillis() + hideKbdIconDelay);
}
private class KbdIconHiderRunnable implements Runnable {
public void run() {
kbdIcon.hide();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
Log.e (TAG, "onCreateOptionsMenu called");
try {
getMenuInflater().inflate(R.menu.connectedmenu, menu);
// Check the proper input method item.
Menu inputMenu = menu.findItem(R.id.menuInputMethod).getSubMenu();
inputMenu.findItem(inputHandlerIdToXmlId (connection.getInputMethod())).setChecked(true);
// Set the text of the Extra Keys menu item appropriately.
if (connection.getExtraKeysToggleType() == Constants.EXTRA_KEYS_ON)
menu.findItem(R.id.menuExtraKeys).setTitle(R.string.extra_keys_disable);
else
menu.findItem(R.id.menuExtraKeys).setTitle(R.string.extra_keys_enable);
} catch (NullPointerException e) {
Log.e (TAG, "There was an error: " + e.getMessage());
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
int itemID = menuItem.getItemId();
switch (itemID) {
case R.id.menuExtraKeys:
if (connection.getExtraKeysToggleType() == Constants.EXTRA_KEYS_ON) {
connection.setExtraKeysToggleType(Constants.EXTRA_KEYS_OFF);
menuItem.setTitle(R.string.extra_keys_enable);
setExtraKeysVisibility(View.GONE, false);
} else {
connection.setExtraKeysToggleType(Constants.EXTRA_KEYS_ON);
menuItem.setTitle(R.string.extra_keys_disable);
setExtraKeysVisibility(View.VISIBLE, false);
extraKeysHidden = false;
}
setKeyStowDrawableAndVisibility();
connection.saveToSharedPreferences(getApplicationContext());
return true;
case R.id.menuDisconnect:
canvas.disconnectAndCleanUp();
finish();
return true;
case R.id.menuSendCAD:
canvas.getKeyboard().keyEvent(112, new KeyEvent(KeyEvent.ACTION_DOWN, 112),
RemoteKeyboard.CTRL_ON_MASK|RemoteKeyboard.ALT_ON_MASK);
canvas.getKeyboard().keyEvent(112, new KeyEvent(KeyEvent.ACTION_UP, 112));
return true;
case R.id.menuHelpInputMethod:
showDialog(R.id.menuHelpInputMethod);
return true;
default:
InputHandler newInputHandler = inputHandlerIdMap.get(menuItem.getItemId());
if (newInputHandler != null) {
inputHandler = newInputHandler;
connection.setInputMethod(newInputHandler.getId());
menuItem.setChecked(true);
displayInputModeInfo(true);
connection.saveToSharedPreferences(getApplicationContext());
return true;
}
}
return super.onOptionsItemSelected(menuItem);
}
@Override
public void onTextSelected(String selectedString) {
canvas.progressDialog.show();
connection.setVmname(canvas.vmNameToId.get(selectedString));
connection.saveToSharedPreferences(this);
synchronized (canvas.spicecomm) {
canvas.spicecomm.notify();
}
}
@Override
public void onTextObtained(String obtainedString) {
canvas.progressDialog.show();
connection.setPassword(obtainedString);
synchronized (canvas.spicecomm) {
canvas.spicecomm.notify();
}
}
}