com.undatech.opaque.RemoteCanvasActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.undatech.opaque.RemoteCanvasActivity.java

Source

/**
 * 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 java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.http.util.ByteArrayBuffer;

import com.gstreamer.GStreamer;
import com.iiordanov.android.zoomer.ZoomControls;
import com.undatech.opaque.R;
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.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 {
    private final static String TAG = "RemoteCanvasActivity";

    public RemoteCanvas canvas;

    private InputHandler inputHandler;
    Map<Integer, InputHandler> inputHandlerIdMap;

    private ConnectionSettings connection;

    ZoomControls kbdIcon;
    Handler 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;

    @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);

        handler = new Handler();
        setContentView(R.layout.canvas);
        canvas = (RemoteCanvas) findViewById(R.id.canvas);

        Intent i = getIntent();
        String vvFileName = startSessionFromVvFile(i);
        if (vvFileName == null) {
            android.util.Log.d(TAG, "Initializing session from connection settings.");
            connection = (ConnectionSettings) i.getSerializableExtra("com.undatech.opaque.ConnectionSettings");
            canvas.initialize(connection);
        } else {
            canvas.initialize(vvFileName, connection);
        }

        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;
            }
        });

        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);
            }
        });

        // 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");
        setIntent(i);
        canvas.disconnectAndCleanUp();
        startSessionFromVvFile(i);
    }

    /**
     * Launches a remote desktop session using a .vv file.
     * @param i
     * @return the vv file name or NULL if no file was discovered.
     */
    private String startSessionFromVvFile(Intent i) {
        final Uri data = i.getData();
        String vvFileName = null;

        android.util.Log.d(TAG, "got intent: " + i.toString());

        if (data != null) {
            android.util.Log.d(TAG, "got data: " + data.toString());

            if (data.toString().startsWith("http")) {
                android.util.Log.d(TAG, "Intent is with http scheme.");
                final String tempVvFile = getFilesDir() + "/tempfile.vv";
                vvFileName = tempVvFile;
                // Spin up a thread to grab the file over the network.
                Thread t = new Thread() {
                    @Override
                    public void run() {
                        try {
                            URL url = new URL(data.toString());
                            File file = new File(tempVvFile);

                            URLConnection ucon = url.openConnection();
                            InputStream is = ucon.getInputStream();
                            BufferedInputStream bis = new BufferedInputStream(is);

                            ByteArrayBuffer baf = new ByteArrayBuffer(3000);
                            int current = 0;
                            while ((current = bis.read()) != -1) {
                                baf.append((byte) current);
                            }

                            FileOutputStream fos = new FileOutputStream(file);
                            fos.write(baf.toByteArray());
                            fos.close();

                            synchronized (RemoteCanvasActivity.this) {
                                RemoteCanvasActivity.this.notify();
                            }
                        } catch (Exception e) {
                        }
                    }
                };
                t.start();

                synchronized (this) {
                    try {
                        this.wait(5000);
                    } catch (InterruptedException e) {
                        vvFileName = null;
                        e.printStackTrace();
                    }
                }
            } else if (data.toString().startsWith("file")) {
                android.util.Log.d(TAG, "Intent is with file scheme.");
                vvFileName = data.getPath();
            } else if (data.toString().startsWith("content")) {
                android.util.Log.d(TAG, "Intent is with content scheme.");

                String[] projection = { MediaStore.MediaColumns.DATA };
                ContentResolver resolver = getApplicationContext().getContentResolver();
                Cursor cursor = resolver.query(data, projection, null, null, null);
                if (cursor != null) {
                    if (cursor.moveToFirst()) {
                        vvFileName = cursor.getString(0);
                    }
                    cursor.close();
                }
            }

            File f = new File(vvFileName);
            android.util.Log.d(TAG, "got filename: " + vvFileName);

            if (f.exists()) {
                android.util.Log.d(TAG, "Initializing session from vv file: " + vvFileName);
                connection = new ConnectionSettings(Constants.DEFAULT_SETTINGS_FILE);
                connection.loadFromSharedPreferences(getApplicationContext());
            } else {
                vvFileName = null;
                // 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 vvFileName;
    }

    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);
    }
}