com.undatech.opaque.RemoteCanvas.java Source code

Java tutorial

Introduction

Here is the source code for com.undatech.opaque.RemoteCanvas.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.FileOutputStream;
import java.util.ArrayList;
import java.util.Timer;

import android.app.Activity;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.text.ClipboardManager;
import android.text.Editable;
import android.text.InputType;
import android.text.Selection;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.KeyEvent;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.ImageView;
import android.widget.Toast;
import android.graphics.BitmapFactory;
import android.graphics.Bitmap;
import android.graphics.RectF;

import com.undatech.opaque.R;
import com.undatech.opaque.SpiceCommunicator;
import com.undatech.opaque.input.RemoteKeyboard;
import com.undatech.opaque.input.RemotePointer;
import com.undatech.opaque.input.RemoteSpiceKeyboard;
import com.undatech.opaque.input.RemoteSpicePointer;
import com.undatech.opaque.dialogs.*;

public class RemoteCanvas extends ImageView implements SelectTextElementFragment.OnFragmentDismissedListener,
        GetTextFragment.OnFragmentDismissedListener {
    private final static String TAG = "RemoteCanvas";

    public Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            FragmentManager fm;
            switch (msg.what) {
            case Constants.VV_FILE_ERROR:
                disconnectAndShowMessage(R.string.vv_file_error, R.string.error_dialog_title);
                break;
            case Constants.NO_VM_FOUND_FOR_USER:
                disconnectAndShowMessage(R.string.error_no_vm_found_for_user, R.string.error_dialog_title);
                break;
            case Constants.OVIRT_SSL_HANDSHAKE_FAILURE:
                disconnectAndShowMessage(R.string.error_ovirt_ssl_handshake_failure, R.string.error_dialog_title);
                break;
            case Constants.VM_LOOKUP_FAILED:
                disconnectAndShowMessage(R.string.error_vm_lookup_failed, R.string.error_dialog_title);
                break;
            case Constants.OVIRT_AUTH_FAILURE:
                disconnectAndShowMessage(R.string.error_ovirt_auth_failure, R.string.error_dialog_title);
                break;
            case Constants.VM_LAUNCHED:
                disconnectAndShowMessage(R.string.info_vm_launched_on_stand_by, R.string.info_dialog_title);
                break;
            case Constants.LAUNCH_VNC_VIEWER:
                /* TODO: Implement:
                             android.util.Log.d(TAG, "Trying to launch VNC viewer");
                             Intent intent = new Intent ();
                              String intentURI = "vnc://" + address + ":" + port + "?password=" + password;
                              try {
                                  intent = Intent.parseUri(intentURI, Intent.URI_INTENT_SCHEME);
                              } catch (URISyntaxException e) {
                              }
                    
                              myself.canvas.getContext().startActivity(intent);
                              disconnectAndCleanup();
                              finish();
                */
                disconnectAndShowMessage(R.string.error_no_vnc_support_yet, R.string.error_dialog_title);
                break;
            case Constants.GET_PASSWORD:
                fm = ((FragmentActivity) getContext()).getSupportFragmentManager();
                GetTextFragment getPassword = GetTextFragment.newInstance(
                        RemoteCanvas.this.getContext().getString(R.string.enter_password), RemoteCanvas.this, true);
                // TODO: Add OK button.
                //newFragment.setCancelable(false);
                getPassword.show(fm, "getPassword");
                break;
            case Constants.DIALOG_DISPLAY_VMS:
                fm = ((FragmentActivity) getContext()).getSupportFragmentManager();
                SelectTextElementFragment displayVms = SelectTextElementFragment.newInstance(
                        RemoteCanvas.this.getContext().getString(R.string.select_vm_title), spicecomm.getVmNames(),
                        RemoteCanvas.this);
                displayVms.setCancelable(false);
                displayVms.show(fm, "selectVm");
                break;
            case Constants.SPICE_CONNECT_SUCCESS:
                spiceUpdateReceived = true;
                synchronized (spicecomm) {
                    spicecomm.notifyAll();
                }
                break;
            case Constants.SPICE_CONNECT_FAILURE:
                // If we were intending to maintainConnection, and the connection failed, show an error message.
                if (stayConnected) {
                    if (!spiceUpdateReceived) {
                        disconnectAndShowMessage(R.string.error_ovirt_unable_to_connect,
                                R.string.error_dialog_title);
                    } else {
                        disconnectAndShowMessage(R.string.error_connection_interrupted,
                                R.string.error_dialog_title);
                    }
                }
                break;
            }
        }
    };

    // Current connection parameters
    private ConnectionSettings settings;

    // Indicates whether we intend to maintain the connection.
    private boolean stayConnected = true;

    // Variable indicating that we are currently moving the cursor in one of the input modes.
    public boolean cursorBeingMoved = false;

    // The drawable of this ImageView.
    public CanvasDrawableContainer myDrawable = null;

    // The class that provides zooming functions to the canvas.
    public CanvasZoomer canvasZoomer;

    // The remote pointer and keyboard
    private RemotePointer pointer;
    private RemoteKeyboard keyboard;

    // The class that abstracts communication with the SPICE backend.
    SpiceCommunicator spicecomm;

    // Used to set the contents of the clipboard.
    ClipboardManager clipboard;
    Timer clipboardMonitorTimer;
    ClipboardMonitor clipboardMonitor;
    public boolean serverJustCutText = false;

    // Indicates that an update from the SPICE server was received.
    boolean spiceUpdateReceived = false;

    /*
     * These variables indicate how far the top left corner of the visible screen
     * has scrolled down and to the right of the top corner of the remote desktop.
     */
    int absX = 0;
    int absY = 0;

    /*
     * How much to shift coordinates over when converting from full to view coordinates.
     */
    float shiftX = 0;
    float shiftY = 0;

    /*
     * This variable holds the height of the visible rectangle of the screen. It is used to keep track
     * of how much of the screen is hidden by the soft keyboard if any.
     */
    int visibleHeight = -1;

    /*
     * These variables contain the width and height of the display in pixels
     */
    int displayWidth = 0;
    int displayHeight = 0;
    float displayDensity = 0;

    /*
     * Variable used for BB workarounds.
     */
    boolean bb = false;

    public RemoteCanvas(final Context context, AttributeSet attrSet) {
        super(context, attrSet);
        clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
        final Display display = ((Activity) context).getWindow().getWindowManager().getDefaultDisplay();
        displayWidth = display.getWidth();
        displayHeight = display.getHeight();
        DisplayMetrics metrics = new DisplayMetrics();
        display.getMetrics(metrics);
        displayDensity = metrics.density;

        canvasZoomer = new CanvasZoomer(this);
        setScaleType(ImageView.ScaleType.MATRIX);

        if (android.os.Build.MODEL.contains("BlackBerry") || android.os.Build.BRAND.contains("BlackBerry")
                || android.os.Build.MANUFACTURER.contains("BlackBerry")) {
            bb = true;
        }
    }

    /**
     * Checks whether the device has networking and quits with an error if it doesn't.
     */
    private void checkNetworkConnectivity() {
        ConnectivityManager cm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
        if (activeNetwork == null || !activeNetwork.isAvailable() || !activeNetwork.isConnected()) {
            disconnectAndShowMessage(R.string.error_not_connected_to_network, R.string.error_dialog_title);
        }
    }

    /**
     * Initializes the clipboard monitor.
     */
    private void initializeClipboardMonitor() {
        clipboardMonitor = new ClipboardMonitor(getContext(), this);
        if (clipboardMonitor != null) {
            clipboardMonitorTimer = new Timer();
            if (clipboardMonitorTimer != null) {
                clipboardMonitorTimer.schedule(clipboardMonitor, 0, 500);
            }
        }
    }

    /**
     * Initialize the canvas to show the remote desktop
     */
    void initialize(final String vvFileName, final ConnectionSettings settings) {
        checkNetworkConnectivity();
        initializeClipboardMonitor();
        this.settings = settings;

        Thread cThread = new Thread() {
            @Override
            public void run() {
                try {
                    spicecomm = new SpiceCommunicator(getContext(), RemoteCanvas.this,
                            settings.isRequestingNewDisplayResolution());
                    pointer = new RemoteSpicePointer(spicecomm, RemoteCanvas.this, handler);
                    keyboard = new RemoteSpiceKeyboard(getResources(), spicecomm, RemoteCanvas.this, handler,
                            settings.getLayoutMap());
                    spicecomm.setHandler(handler);
                    spicecomm.startSessionFromVvFile(vvFileName, settings.isAudioPlaybackEnabled());
                } catch (Throwable e) {
                    if (stayConnected) {
                        e.printStackTrace();
                        android.util.Log.e(TAG, e.toString());
                        if (e instanceof OutOfMemoryError) {
                            disposeDrawable();
                            disconnectAndShowMessage(R.string.error_out_of_memory, R.string.error_dialog_title);
                        }
                    }
                }
            }
        };
        cThread.start();
    }

    /**
     * Initialize the canvas to show the remote desktop
     */
    void initialize(final ConnectionSettings settings) {
        checkNetworkConnectivity();
        initializeClipboardMonitor();
        this.settings = settings;

        Thread cThread = new Thread() {
            @Override
            public void run() {
                try {
                    spicecomm = new SpiceCommunicator(getContext(), RemoteCanvas.this,
                            settings.isRequestingNewDisplayResolution());
                    pointer = new RemoteSpicePointer(spicecomm, RemoteCanvas.this, handler);
                    keyboard = new RemoteSpiceKeyboard(getResources(), spicecomm, RemoteCanvas.this, handler,
                            settings.getLayoutMap());
                    spicecomm.setHandler(handler);

                    // Obtain user's password if necessary.
                    if (settings.getPassword().equals("")) {
                        android.util.Log.i(TAG, "Displaying a dialog to obtain user's password.");
                        handler.sendEmptyMessage(Constants.GET_PASSWORD);
                        synchronized (spicecomm) {
                            spicecomm.wait();
                        }
                    }

                    String ovirtCaFile = null;
                    if (settings.isUsingCustomOvirtCa()) {
                        ovirtCaFile = settings.getOvirtCaFile();
                    } else {
                        String caBundleFileName = getContext().getFilesDir() + "/ca-bundle.crt";
                        ovirtCaFile = caBundleFileName;
                    }

                    // If not VM name is specified, then get a list of VMs and let the user pick one.
                    if (settings.getVmname().equals("")) {
                        int success = spicecomm.fetchOvirtVmNames(settings.getHostname(), settings.getUser(),
                                settings.getPassword(), ovirtCaFile, settings.isSslStrict());
                        // VM retrieval was unsuccessful we do not continue.
                        ArrayList<String> vmNames = spicecomm.getVmNames();
                        if (success != 0 || vmNames.isEmpty()) {
                            return;
                        } else {
                            // If there is just one VM, pick it and skip the dialog.
                            if (vmNames.size() == 1) {
                                settings.setVmname(vmNames.get(0));
                                settings.saveToSharedPreferences(getContext());
                            } else {
                                while (settings.getVmname().equals("")) {
                                    android.util.Log.i(TAG, "Displaying a dialog with VMs to the user.");
                                    handler.sendEmptyMessage(Constants.DIALOG_DISPLAY_VMS);
                                    synchronized (spicecomm) {
                                        spicecomm.wait();
                                    }
                                }
                            }
                        }
                    }

                    spicecomm.connectOvirt(settings.getHostname(), settings.getVmname(), settings.getUser(),
                            settings.getPassword(), ovirtCaFile, settings.isAudioPlaybackEnabled(),
                            settings.isSslStrict());

                    try {
                        synchronized (spicecomm) {
                            spicecomm.wait(32000);
                        }
                    } catch (InterruptedException e) {
                    }

                    if (!spiceUpdateReceived) {
                        handler.sendEmptyMessage(Constants.SPICE_CONNECT_FAILURE);
                    }
                } catch (Throwable e) {
                    if (stayConnected) {
                        e.printStackTrace();
                        android.util.Log.e(TAG, e.toString());

                        if (e instanceof OutOfMemoryError) {
                            disposeDrawable();
                            disconnectAndShowMessage(R.string.error_out_of_memory, R.string.error_dialog_title);
                        }
                    }
                }
            }
        };
        cThread.start();
    }

    /**
     * Retreives the requested remote width.
     */
    int getDesiredWidth() {
        int w = getWidth();
        android.util.Log.e(TAG, "Width requested: " + w);
        return w;
    }

    /**
     * Retreives the requested remote height.
     */
    int getDesiredHeight() {
        int h = getHeight();
        android.util.Log.e(TAG, "Height requested: " + h);
        return h;
    }

    public boolean getMouseFollowPan() {
        // TODO: Fix
        return true; //connection.getFollowPan();
    }

    public void displayShortToastMessage(final CharSequence message) {
        screenMessage = message;
        handler.removeCallbacks(showMessage);
        handler.post(showMessage);
    }

    public void displayShortToastMessage(final int messageID) {
        screenMessage = getResources().getText(messageID);
        handler.removeCallbacks(showMessage);
        handler.post(showMessage);
    }

    void disconnectAndShowMessage(final int messageId, final int titleId) {
        disconnectAndCleanUp();
        handler.post(new Runnable() {
            public void run() {
                MessageDialogs.displayMessageAndFinish(getContext(), messageId, titleId);
            }
        });
    }

    /**
     * Set the device clipboard text with the string parameter.
     * @param readServerCutText set the device clipboard to the text in this parameter.
     */
    public void setClipboardText(String s) {
        if (s != null && s.length() > 0) {
            clipboard.setText(s);
        }
    }

    void disposeDrawable() {
        if (myDrawable != null)
            myDrawable.destroy();
        myDrawable = null;
        System.gc();
    }

    CanvasDrawableContainer reallocateDrawable(int width, int height) {
        disposeDrawable();
        try {
            myDrawable = new CanvasDrawableContainer(width, height);
        } catch (Throwable e) {
            disconnectAndShowMessage(R.string.error_out_of_memory, R.string.error_dialog_title);
        }
        // TODO: Implement cursor integration.
        initializeSoftCursor();
        // Set the drawable for the canvas, now that we have it (re)initialized.
        handler.post(drawableSetter);
        computeShiftFromFullToView();
        return myDrawable;
    }

    public void disconnectAndCleanUp() {
        stayConnected = false;

        if (keyboard != null) {
            // Tell the server to release any meta keys.
            keyboard.clearOnScreenMetaState();
            keyboard.keyEvent(0, new KeyEvent(KeyEvent.ACTION_UP, 0));
        }

        if (spicecomm != null)
            spicecomm.close();

        if (handler != null) {
            handler.removeCallbacksAndMessages(null);
        }

        if (clipboardMonitorTimer != null) {
            clipboardMonitorTimer.cancel();
            // Occasionally causes a NullPointerException
            //clipboardMonitorTimer.purge();
            clipboardMonitorTimer = null;
        }

        clipboardMonitor = null;
        clipboard = null;

        try {
            if (myDrawable != null && myDrawable.bitmap != null) {
                String location = settings.getFilename();
                FileOutputStream out = new FileOutputStream(getContext().getFilesDir() + "/" + location + ".png");
                Bitmap tmp = Bitmap.createScaledBitmap(myDrawable.bitmap, 360, 300, true);
                myDrawable.bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
                out.close();
                tmp.recycle();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        disposeDrawable();
    }

    /**
     * Make sure the remote pointer is visible
     */
    public void movePanToMakePointerVisible() {
        if (spicecomm != null) {
            int x = pointer.getX();
            int y = pointer.getY();
            int newAbsX = absX;
            int newAbsY = absY;

            int wthresh = 30;
            int hthresh = 30;
            int visibleWidth = getVisibleDesktopWidth();
            int visibleHeight = getVisibleDesktopHeight();
            int desktopWidth = getDesktopWidth();
            int desktopHeight = getDesktopHeight();
            if (x - absX >= visibleWidth - wthresh) {
                newAbsX = x - (visibleWidth - wthresh);
            } else if (x < absX + wthresh) {
                newAbsX = x - wthresh;
            }
            if (y - absY >= visibleHeight - hthresh) {
                newAbsY = y - (visibleHeight - hthresh);
            } else if (y < absY + hthresh) {
                newAbsY = y - hthresh;
            }
            if (newAbsX < 0) {
                newAbsX = 0;
            }
            if (newAbsX + visibleWidth > desktopWidth) {
                newAbsX = desktopWidth - visibleWidth;
            }
            if (newAbsY < 0) {
                newAbsY = 0;
            }
            if (newAbsY + visibleHeight > desktopHeight) {
                newAbsY = desktopHeight - visibleHeight;
            }
            absolutePan(newAbsX, newAbsY);
        }
    }

    /**
     * Relative pan.
     * @param dX
     * @param dY
     */
    public void relativePan(int dX, int dY) {
        double zoomFactor = getZoomFactor();
        absolutePan((int) (absX + dX / zoomFactor), (int) (absY + dY / zoomFactor));
    }

    /**
     * Absolute pan.
     * @param x
     * @param y
     */
    public void absolutePan(int x, int y) {
        if (canvasZoomer != null) {
            int vW = getVisibleDesktopWidth();
            int vH = getVisibleDesktopHeight();
            int w = getDesktopWidth();
            int h = getDesktopHeight();
            if (x + vW > w)
                x = w - vW;
            if (y + vH > h)
                y = h - vH;
            if (x < 0)
                x = 0;
            if (y < 0)
                y = 0;
            absX = x;
            absY = y;
            resetScroll();
        }
    }

    /**
     * Reset the canvas's scroll position.
     */
    void resetScroll() {
        float scale = getZoomFactor();
        scrollTo((int) ((absX - shiftX) * scale), (int) ((absY - shiftY) * scale));
    }

    /**
     * Computes the X and Y offset for converting coordinates from full-frame coordinates to view coordinates.
     */
    public void computeShiftFromFullToView() {
        shiftX = (spicecomm.framebufferWidth() - getWidth()) / 2;
        shiftY = (spicecomm.framebufferHeight() - getHeight()) / 2;
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (myDrawable != null) {
            pointer.movePointerToMakeVisible();
        }
    }

    /**
     * This runnable displays a message on the screen.
     */
    CharSequence screenMessage;
    private Runnable showMessage = new Runnable() {
        public void run() {
            Toast.makeText(getContext(), screenMessage, Toast.LENGTH_SHORT).show();
        }
    };

    /**
     * This runnable sets the drawable for this ImageView.
     */
    private Runnable drawableSetter = new Runnable() {
        public void run() {
            if (myDrawable != null)
                setImageDrawable(myDrawable);
            canvasZoomer.resetScaling();
        }
    };

    /**
     * Causes a redraw of the bitmapData to happen at the indicated coordinates.
     */
    public void reDraw(int x, int y, int w, int h) {
        float scale = getZoomFactor();
        float shiftedX = x - shiftX;
        float shiftedY = y - shiftY;
        // Make the box slightly larger to avoid artifacts due to truncation errors.
        postInvalidate((int) ((shiftedX - 1) * scale), (int) ((shiftedY - 1) * scale),
                (int) ((shiftedX + w + 1) * scale), (int) ((shiftedY + h + 1) * scale));
    }

    /**
     * This is a float-accepting version of reDraw().
     * Causes a redraw of the bitmapData to happen at the indicated coordinates.
     */
    public void reDraw(float x, float y, float w, float h) {
        float scale = getZoomFactor();
        float shiftedX = x - shiftX;
        float shiftedY = y - shiftY;
        // Make the box slightly larger to avoid artifacts due to truncation errors.
        postInvalidate((int) ((shiftedX - 1.f) * scale), (int) ((shiftedY - 1.f) * scale),
                (int) ((shiftedX + w + 1.f) * scale), (int) ((shiftedY + h + 1.f) * scale));
    }

    /**
     * Redraws the location of the remote pointer.
     */
    public void reDrawRemotePointer() {
        if (myDrawable != null) {
            myDrawable.moveCursorRect(pointer.getX(), pointer.getY());
            RectF r = myDrawable.getCursorRect();
            reDraw(r.left, r.top, r.width(), r.height());
        }
    }

    /**
     * Moves soft cursor into a particular location.
     * @param x
     * @param y
     */

    synchronized void softCursorMove(int x, int y) {
        if (myDrawable.isNotInitSoftCursor()) {
            initializeSoftCursor();
        }

        if (!cursorBeingMoved) {
            pointer.setX(x);
            pointer.setY(y);
            RectF prevR = new RectF(myDrawable.getCursorRect());
            // Move the cursor.
            myDrawable.moveCursorRect(x, y);
            // Show the cursor.
            RectF r = myDrawable.getCursorRect();
            reDraw(r.left, r.top, r.width(), r.height());
            reDraw(prevR.left, prevR.top, prevR.width(), prevR.height());
        }
    }

    void initializeSoftCursor() {
        Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.cursor);
        int w = bm.getWidth();
        int h = bm.getHeight();
        int[] tempPixels = new int[w * h];
        bm.getPixels(tempPixels, 0, w, 0, 0, w, h);
        // Set cursor rectangle as well.
        myDrawable.setCursorRect(pointer.getX(), pointer.getY(), w, h, 0, 0);
        // Set softCursor to whatever the resource is.
        myDrawable.setSoftCursor(tempPixels);
        bm.recycle();
    }

    public RemotePointer getPointer() {
        return pointer;
    }

    public RemoteKeyboard getKeyboard() {
        return keyboard;
    }

    public float getZoomFactor() {
        if (canvasZoomer == null)
            return 1;
        return canvasZoomer.getZoomFactor();
    }

    public int getVisibleDesktopWidth() {
        return (int) ((double) getWidth() / getZoomFactor() + 0.5);
    }

    public int getVisibleDesktopHeight() {
        if (visibleHeight > 0)
            return (int) ((double) visibleHeight / getZoomFactor() + 0.5);
        else
            return (int) ((double) getHeight() / getZoomFactor() + 0.5);
    }

    public void setVisibleDesktopHeight(int newHeight) {
        visibleHeight = newHeight;
    }

    public int getDesktopWidth() {
        return spicecomm.framebufferWidth();
    }

    public int getDesktopHeight() {
        return spicecomm.framebufferHeight();
    }

    public float getMinimumScale() {
        if (myDrawable != null) {
            return myDrawable.getMinimumScale(getWidth(), getHeight());
        } else
            return 1.f;
    }

    public int getAbsX() {
        return absX;
    }

    public int getAbsY() {
        return absY;
    }

    public float getShiftX() {
        return shiftX;
    }

    public float getShiftY() {
        return shiftY;
    }

    public float getDisplayDensity() {
        return displayDensity;
    }

    public float getDisplayWidth() {
        return displayWidth;
    }

    public float getDisplayHeight() {
        return displayHeight;
    }

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        android.util.Log.d(TAG, "onCreateInputConnection called");
        int version = android.os.Build.VERSION.SDK_INT;
        BaseInputConnection bic = null;
        if (!bb && version >= Build.VERSION_CODES.JELLY_BEAN) {
            bic = new BaseInputConnection(this, false) {
                final static String junk_unit = "%%%%%%%%%%";
                final static int multiple = 1000;
                Editable e;

                @Override
                public Editable getEditable() {
                    if (e == null) {
                        int numTotalChars = junk_unit.length() * multiple;
                        String junk = new String();
                        for (int i = 0; i < multiple; i++) {
                            junk += junk_unit;
                        }
                        e = Editable.Factory.getInstance().newEditable(junk);
                        Selection.setSelection(e, numTotalChars);
                        if (RemoteCanvas.this.keyboard != null) {
                            RemoteCanvas.this.keyboard.skippedJunkChars = false;
                        }
                    }
                    return e;
                }
            };
        } else {
            bic = new BaseInputConnection(this, false);
        }

        outAttrs.actionLabel = null;
        outAttrs.inputType = InputType.TYPE_NULL;
        /* TODO: If people complain about kbd not working, this is a possible workaround to
         * test and add an option for.
        // Workaround for IME's that don't support InputType.TYPE_NULL.
        if (version >= 11) {
        outAttrs.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
        outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN;
        }*/
        return bic;
    }

    @Override
    public void onTextSelected(String selectedString) {
        settings.setVmname(selectedString);
        settings.saveToSharedPreferences(getContext());
        synchronized (spicecomm) {
            spicecomm.notify();
        }
    }

    @Override
    public void onTextObtained(String obtainedString) {
        settings.setPassword(obtainedString);
        synchronized (spicecomm) {
            spicecomm.notify();
        }
    }

    /**
     * Used to wait until getWidth and getHeight return sane values.
     */
    public void waitUntilInflated() {
        synchronized (this) {
            while (getWidth() == 0 || getHeight() == 0) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * Used to detect when the view is inflated to a sane size other than 0x0.
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w > 0 && h > 0) {
            synchronized (this) {
                this.notify();
            }
        }
    }
}