@Override
  public boolean OnVerifiyCertificate(String subject, String issuer, String fingerprint) {

    // see if global settings says accept all
    if (GlobalSettings.getAcceptAllCertificates()) return true;

    // this is where the return code of our dialog will be stored
    callbackDialogResult = false;

    // set message
    String msg = getResources().getString(R.string.dlg_msg_verify_certificate);
    msg = msg + "\n\nSubject: " + subject + "\nIssuer: " + issuer + "\nFingerprint: " + fingerprint;
    dlgVerifyCertificate.setMessage(msg);

    // start dialog in UI thread
    uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG, dlgVerifyCertificate));

    // wait for result
    try {
      synchronized (dlgVerifyCertificate) {
        dlgVerifyCertificate.wait();
      }
    } catch (InterruptedException e) {
    }

    return callbackDialogResult;
  }
 @Override
 public void onScrollChanged(ScrollView2D scrollView, int x, int y, int oldx, int oldy) {
   zoomControls.setIsZoomInEnabled(!sessionView.isAtMaxZoom());
   zoomControls.setIsZoomOutEnabled(!sessionView.isAtMinZoom());
   if (!GlobalSettings.getHideZoomControls() && zoomControls.getVisibility() != View.VISIBLE)
     zoomControls.show();
   resetZoomControlsAutoHideTimeout();
 }
public class SessionActivity extends Activity
    implements LibFreeRDP.UIEventListener,
        KeyboardView.OnKeyboardActionListener,
        ScrollView2D.ScrollView2DListener,
        KeyboardMapper.KeyProcessingListener,
        SessionView.SessionViewListener,
        TouchPointerView.TouchPointerListener {
  private class UIHandler extends Handler {

    public static final int REFRESH_SESSIONVIEW = 1;
    public static final int DISPLAY_TOAST = 2;
    public static final int HIDE_ZOOMCONTROLS = 3;
    public static final int SEND_MOVE_EVENT = 4;
    public static final int SHOW_DIALOG = 5;
    public static final int GRAPHICS_CHANGED = 6;
    public static final int SCROLLING_REQUESTED = 7;

    UIHandler() {
      super();
    }

    @Override
    public void handleMessage(Message msg) {
      switch (msg.what) {
        case GRAPHICS_CHANGED:
          {
            sessionView.onSurfaceChange(session);
            scrollView.requestLayout();
            break;
          }
        case REFRESH_SESSIONVIEW:
          {
            sessionView.invalidateRegion();
            break;
          }
        case DISPLAY_TOAST:
          {
            Toast errorToast =
                Toast.makeText(getApplicationContext(), msg.obj.toString(), Toast.LENGTH_LONG);
            errorToast.show();
            break;
          }
        case HIDE_ZOOMCONTROLS:
          {
            zoomControls.hide();
            break;
          }
        case SEND_MOVE_EVENT:
          {
            LibFreeRDP.sendCursorEvent(
                session.getInstance(), msg.arg1, msg.arg2, Mouse.getMoveEvent());
            break;
          }
        case SHOW_DIALOG:
          {
            // create and show the dialog
            ((Dialog) msg.obj).show();
            break;
          }
        case SCROLLING_REQUESTED:
          {
            int scrollX = 0;
            int scrollY = 0;
            float[] pointerPos = touchPointerView.getPointerPosition();

            if (pointerPos[0] > (screen_width - touchPointerView.getPointerWidth()))
              scrollX = SCROLLING_DISTANCE;
            else if (pointerPos[0] < 0) scrollX = -SCROLLING_DISTANCE;

            if (pointerPos[1] > (screen_height - touchPointerView.getPointerHeight()))
              scrollY = SCROLLING_DISTANCE;
            else if (pointerPos[1] < 0) scrollY = -SCROLLING_DISTANCE;

            scrollView.scrollBy(scrollX, scrollY);

            // see if we reached the min/max scroll positions
            if (scrollView.getScrollX() == 0
                || scrollView.getScrollX() == (sessionView.getWidth() - scrollView.getWidth()))
              scrollX = 0;
            if (scrollView.getScrollY() == 0
                || scrollView.getScrollY() == (sessionView.getHeight() - scrollView.getHeight()))
              scrollY = 0;

            if (scrollX != 0 || scrollY != 0)
              uiHandler.sendEmptyMessageDelayed(SCROLLING_REQUESTED, SCROLLING_TIMEOUT);
            else Log.v(TAG, "Stopping auto-scroll");
            break;
          }
      }
    }
  }

  private class PinchZoomListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    private float scaleFactor = 1.0f;

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
      scrollView.setScrollEnabled(false);
      return true;
    }

    @Override
    public boolean onScale(ScaleGestureDetector detector) {

      // calc scale factor
      scaleFactor *= detector.getScaleFactor();
      scaleFactor =
          Math.max(
              SessionView.MIN_SCALE_FACTOR, Math.min(scaleFactor, SessionView.MAX_SCALE_FACTOR));
      sessionView.setZoom(scaleFactor);

      if (!sessionView.isAtMinZoom() && !sessionView.isAtMaxZoom()) {
        // transform scroll origin to the new zoom space
        float transOriginX = scrollView.getScrollX() * detector.getScaleFactor();
        float transOriginY = scrollView.getScrollY() * detector.getScaleFactor();

        // transform center point to the zoomed space
        float transCenterX =
            (scrollView.getScrollX() + detector.getFocusX()) * detector.getScaleFactor();
        float transCenterY =
            (scrollView.getScrollY() + detector.getFocusY()) * detector.getScaleFactor();

        // scroll by the difference between the distance of the transformed center/origin point and
        // their old distance (focusX/Y)
        scrollView.scrollBy(
            (int) ((transCenterX - transOriginX) - detector.getFocusX()),
            (int) ((transCenterY - transOriginY) - detector.getFocusY()));
      }

      return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector de) {
      scrollView.setScrollEnabled(true);
    }
  }

  private class LibFreeRDPBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
      // still got a valid session?
      if (session == null) return;

      // is this event for the current session?
      if (session.getInstance() != intent.getExtras().getInt(GlobalApp.EVENT_PARAM, -1)) return;

      switch (intent.getExtras().getInt(GlobalApp.EVENT_TYPE, -1)) {
        case GlobalApp.FREERDP_EVENT_CONNECTION_SUCCESS:
          OnConnectionSuccess(context);
          break;

        case GlobalApp.FREERDP_EVENT_CONNECTION_FAILURE:
          OnConnectionFailure(context);
          break;
        case GlobalApp.FREERDP_EVENT_DISCONNECTED:
          OnDisconnected(context);
          break;
      }
    }

    private void OnConnectionSuccess(Context context) {
      Log.v(TAG, "OnConnectionSuccess");

      // bind session
      bindSession();

      if (progressDialog != null) {
        progressDialog.dismiss();
        progressDialog = null;
      }

      // add hostname to history if quick connect was used
      Bundle bundle = getIntent().getExtras();
      if (bundle != null && bundle.containsKey(PARAM_CONNECTION_REFERENCE)) {
        if (ConnectionReference.isHostnameReference(bundle.getString(PARAM_CONNECTION_REFERENCE))) {
          assert session.getBookmark().getType() == BookmarkBase.TYPE_MANUAL;
          String item = session.getBookmark().<ManualBookmark>get().getHostname();
          if (!GlobalApp.getQuickConnectHistoryGateway().historyItemExists(item))
            GlobalApp.getQuickConnectHistoryGateway().addHistoryItem(item);
        }
      }
    }

    private void OnConnectionFailure(Context context) {
      Log.v(TAG, "OnConnectionFailure");

      if (progressDialog != null) {
        progressDialog.dismiss();
        progressDialog = null;
      }

      // post error message on UI thread
      if (!connectCancelledByUser)
        uiHandler.sendMessage(
            Message.obtain(
                null,
                UIHandler.DISPLAY_TOAST,
                getResources().getText(R.string.error_connection_failure)));

      closeSessionActivity(RESULT_CANCELED);
    }

    private void OnDisconnected(Context context) {
      Log.v(TAG, "OnDisconnected");

      if (progressDialog != null) {
        progressDialog.dismiss();
        progressDialog = null;
      }

      session.setUIEventListener(null);
      closeSessionActivity(RESULT_OK);
    }
  }

  public static final String PARAM_CONNECTION_REFERENCE = "conRef";
  public static final String PARAM_INSTANCE = "instance";

  private static final float ZOOMING_STEP = 0.5f;
  private static final int ZOOMCONTROLS_AUTOHIDE_TIMEOUT = 4000;

  // timeout between subsequent scrolling requests when the touch-pointer is at the edge of the
  // session view
  private static final int SCROLLING_TIMEOUT = 50;
  private static final int SCROLLING_DISTANCE = 20;

  private Bitmap bitmap;
  private SessionState session;
  private SessionView sessionView;
  private TouchPointerView touchPointerView;
  private ProgressDialog progressDialog;
  private KeyboardView keyboardView;
  private KeyboardView modifiersKeyboardView;
  private ZoomControls zoomControls;
  private KeyboardMapper keyboardMapper;

  private Keyboard specialkeysKeyboard;
  private Keyboard numpadKeyboard;
  private Keyboard cursorKeyboard;
  private Keyboard modifiersKeyboard;

  private AlertDialog dlgVerifyCertificate;
  private AlertDialog dlgUserCredentials;
  private View userCredView;

  private UIHandler uiHandler;

  private int screen_width;
  private int screen_height;

  private boolean autoScrollTouchPointer = GlobalSettings.getAutoScrollTouchPointer();
  private boolean connectCancelledByUser = false;
  private boolean sessionRunning = false;
  private boolean toggleMouseButtons = false;

  private LibFreeRDPBroadcastReceiver libFreeRDPBroadcastReceiver;

  private static final String TAG = "FreeRDP.SessionActivity";

  private ScrollView2D scrollView;

  // keyboard visibility flags
  private boolean sysKeyboardVisible = false;
  private boolean extKeyboardVisible = false;

  // variables for delayed move event sending
  private static final int MAX_DISCARDED_MOVE_EVENTS = 3;
  private static final int SEND_MOVE_EVENT_TIMEOUT = 150;
  private int discardedMoveEvents = 0;

  private void createDialogs() {
    // build verify certificate dialog
    dlgVerifyCertificate =
        new AlertDialog.Builder(this)
            .setTitle(R.string.dlg_title_verify_certificate)
            .setPositiveButton(
                android.R.string.yes,
                new DialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface dialog, int which) {
                    callbackDialogResult = true;
                    synchronized (dialog) {
                      dialog.notify();
                    }
                  }
                })
            .setNegativeButton(
                android.R.string.no,
                new DialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface dialog, int which) {
                    callbackDialogResult = false;
                    connectCancelledByUser = true;
                    synchronized (dialog) {
                      dialog.notify();
                    }
                  }
                })
            .setCancelable(false)
            .create();

    // build the dialog
    userCredView = getLayoutInflater().inflate(R.layout.credentials, null, true);
    dlgUserCredentials =
        new AlertDialog.Builder(this)
            .setView(userCredView)
            .setTitle(R.string.dlg_title_credentials)
            .setPositiveButton(
                android.R.string.ok,
                new DialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface dialog, int which) {
                    callbackDialogResult = true;
                    synchronized (dialog) {
                      dialog.notify();
                    }
                  }
                })
            .setNegativeButton(
                android.R.string.cancel,
                new DialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface dialog, int which) {
                    callbackDialogResult = false;
                    connectCancelledByUser = true;
                    synchronized (dialog) {
                      dialog.notify();
                    }
                  }
                })
            .setCancelable(false)
            .create();
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // show status bar or make fullscreen?
    if (GlobalSettings.getHideStatusBar())
      getWindow()
          .setFlags(
              WindowManager.LayoutParams.FLAG_FULLSCREEN,
              WindowManager.LayoutParams.FLAG_FULLSCREEN);
    this.setContentView(R.layout.session);

    Log.v(TAG, "Session.onCreate");

    // ATTENTION: We use the onGlobalLayout notification to start our session.
    // This is because only then we can know the exact size of our session when using fit screen
    // accounting for any status bars etc. that Android might throws on us. A bit weird looking
    // but this is the only way ...
    final View activityRootView = findViewById(R.id.session_root_view);
    activityRootView
        .getViewTreeObserver()
        .addOnGlobalLayoutListener(
            new OnGlobalLayoutListener() {
              @Override
              public void onGlobalLayout() {
                screen_width = activityRootView.getWidth();
                screen_height = activityRootView.getHeight();

                // start session
                if (!sessionRunning && getIntent() != null) {
                  processIntent(getIntent());
                  sessionRunning = true;
                }
              }
            });

    sessionView = (SessionView) findViewById(R.id.sessionView);
    sessionView.setScaleGestureDetector(new ScaleGestureDetector(this, new PinchZoomListener()));
    sessionView.setSessionViewListener(this);
    sessionView.requestFocus();

    touchPointerView = (TouchPointerView) findViewById(R.id.touchPointerView);
    touchPointerView.setTouchPointerListener(this);

    keyboardMapper = new KeyboardMapper();
    keyboardMapper.init(this);

    modifiersKeyboard = new Keyboard(getApplicationContext(), R.xml.modifiers_keyboard);
    specialkeysKeyboard = new Keyboard(getApplicationContext(), R.xml.specialkeys_keyboard);
    numpadKeyboard = new Keyboard(getApplicationContext(), R.xml.numpad_keyboard);
    cursorKeyboard = new Keyboard(getApplicationContext(), R.xml.cursor_keyboard);

    // hide keyboard below the sessionView
    keyboardView = (KeyboardView) findViewById(R.id.extended_keyboard);
    keyboardView.setKeyboard(specialkeysKeyboard);
    keyboardView.setOnKeyboardActionListener(this);

    modifiersKeyboardView = (KeyboardView) findViewById(R.id.extended_keyboard_header);
    modifiersKeyboardView.setKeyboard(modifiersKeyboard);
    modifiersKeyboardView.setOnKeyboardActionListener(this);

    scrollView = (ScrollView2D) findViewById(R.id.sessionScrollView);
    scrollView.setScrollViewListener(this);
    uiHandler = new UIHandler();
    libFreeRDPBroadcastReceiver = new LibFreeRDPBroadcastReceiver();

    zoomControls = (ZoomControls) findViewById(R.id.zoomControls);
    zoomControls.hide();
    zoomControls.setOnZoomInClickListener(
        new View.OnClickListener() {
          @Override
          public void onClick(View v) {
            resetZoomControlsAutoHideTimeout();
            zoomControls.setIsZoomInEnabled(sessionView.zoomIn(ZOOMING_STEP));
            zoomControls.setIsZoomOutEnabled(true);
          }
        });
    zoomControls.setOnZoomOutClickListener(
        new View.OnClickListener() {
          @Override
          public void onClick(View v) {
            resetZoomControlsAutoHideTimeout();
            zoomControls.setIsZoomOutEnabled(sessionView.zoomOut(ZOOMING_STEP));
            zoomControls.setIsZoomInEnabled(true);
          }
        });

    toggleMouseButtons = false;

    createDialogs();

    // register freerdp events broadcast receiver
    IntentFilter filter = new IntentFilter();
    filter.addAction(GlobalApp.ACTION_EVENT_FREERDP);
    registerReceiver(libFreeRDPBroadcastReceiver, filter);
  }

  @Override
  protected void onStart() {
    super.onStart();
    Log.v(TAG, "Session.onStart");
  }

  @Override
  protected void onRestart() {
    super.onRestart();
    Log.v(TAG, "Session.onRestart");
  }

  @Override
  protected void onResume() {
    super.onResume();
    Log.v(TAG, "Session.onResume");
  }

  @Override
  protected void onPause() {
    super.onPause();
    Log.v(TAG, "Session.onPause");

    // hide any visible keyboards
    showKeyboard(false, false);
  }

  @Override
  protected void onStop() {
    super.onStop();
    Log.v(TAG, "Session.onStop");
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    Log.v(TAG, "Session.onDestroy");

    // unregister freerdp events broadcast receiver
    unregisterReceiver(libFreeRDPBroadcastReceiver);

    // free session
    GlobalApp.freeSession(session.getInstance());
    session = null;
  }

  @Override
  public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // reload keyboard resources (changed from landscape)
    modifiersKeyboard = new Keyboard(getApplicationContext(), R.xml.modifiers_keyboard);
    specialkeysKeyboard = new Keyboard(getApplicationContext(), R.xml.specialkeys_keyboard);
    numpadKeyboard = new Keyboard(getApplicationContext(), R.xml.numpad_keyboard);
    cursorKeyboard = new Keyboard(getApplicationContext(), R.xml.cursor_keyboard);

    // apply loaded keyboards
    keyboardView.setKeyboard(specialkeysKeyboard);
    modifiersKeyboardView.setKeyboard(modifiersKeyboard);
  }

  private void processIntent(Intent intent) {
    // get either session instance or create one from a bookmark
    Bundle bundle = intent.getExtras();
    if (bundle.containsKey(PARAM_INSTANCE)) {
      int inst = bundle.getInt(PARAM_INSTANCE);
      session = GlobalApp.getSession(inst);
      bitmap = session.getSurface().getBitmap();
      bindSession();
    } else if (bundle.containsKey(PARAM_CONNECTION_REFERENCE)) {
      BookmarkBase bookmark = null;
      String refStr = bundle.getString(PARAM_CONNECTION_REFERENCE);
      if (ConnectionReference.isHostnameReference(refStr)) {
        bookmark = new ManualBookmark();
        bookmark.<ManualBookmark>get().setHostname(ConnectionReference.getHostname(refStr));
      } else if (ConnectionReference.isBookmarkReference(refStr)) {
        if (ConnectionReference.isManualBookmarkReference(refStr))
          bookmark =
              GlobalApp.getManualBookmarkGateway()
                  .findById(ConnectionReference.getManualBookmarkId(refStr));
        else assert false;
      }

      if (bookmark != null) connect(bookmark);
      else closeSessionActivity(RESULT_CANCELED);
    } else {
      // no session found - exit
      closeSessionActivity(RESULT_CANCELED);
    }
  }

  private void connect(BookmarkBase bookmark) {
    session = GlobalApp.createSession(bookmark);
    session.setUIEventListener(this);

    // set writeable data directory
    LibFreeRDP.setDataDirectory(session.getInstance(), getFilesDir().toString());

    BookmarkBase.ScreenSettings screenSettings = session.getBookmark().getActiveScreenSettings();
    Log.v(TAG, "Screen Resolution: " + screenSettings.getResolutionString());
    if (screenSettings.isAutomatic()) {
      if ((getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK)
          >= Configuration.SCREENLAYOUT_SIZE_LARGE) {
        // large screen device i.e. tablet: simply use screen info
        screenSettings.setHeight(screen_height);
        screenSettings.setWidth(screen_width);
      } else {
        // small screen device i.e. phone:
        // Automatic uses the largest side length of the screen and makes a 16:10 resolution setting
        // out of it
        int screenMax = (screen_width > screen_height) ? screen_width : screen_height;
        screenSettings.setHeight(screenMax);
        screenSettings.setWidth((int) ((float) screenMax * 1.6f));
      }
    }

    progressDialog = new ProgressDialog(this);
    progressDialog.setTitle(bookmark.getLabel());
    progressDialog.setMessage(getResources().getText(R.string.dlg_msg_connecting));
    progressDialog.setButton(
        ProgressDialog.BUTTON_NEGATIVE,
        "Cancel",
        new DialogInterface.OnClickListener() {
          @Override
          public void onClick(DialogInterface dialog, int which) {
            connectCancelledByUser = true;
            LibFreeRDP.cancelConnection(session.getInstance());
          }
        });
    progressDialog.setCancelable(false);
    progressDialog.show();

    Thread thread =
        new Thread(
            new Runnable() {
              public void run() {
                session.connect();
              }
            });
    thread.start();
  }

  // binds the current session to the activity by wiring it up with the sessionView and updating all
  // internal objects accordingly
  private void bindSession() {
    Log.v("SessionActivity", "bindSession called");
    session.setUIEventListener(this);
    sessionView.onSurfaceChange(session);
    scrollView.requestLayout();
    keyboardMapper.reset(this);
  }

  // displays either the system or the extended keyboard or non of  them
  private void showKeyboard(boolean showSystemKeyboard, boolean showExtendedKeyboard) {
    // no matter what we are doing ... hide the zoom controls
    // TODO: this is not working correctly as hiding the keyboard issues a onScrollChange
    // notification showing the control again ...
    uiHandler.removeMessages(UIHandler.HIDE_ZOOMCONTROLS);
    if (zoomControls.getVisibility() == View.VISIBLE) zoomControls.hide();

    InputMethodManager mgr;
    if (showSystemKeyboard) {
      // hide extended keyboard
      keyboardView.setVisibility(View.GONE);

      // show system keyboard
      mgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
      if (!mgr.isActive(sessionView))
        Log.e(TAG, "Failed to show system keyboard: SessionView is not the active view!");
      mgr.showSoftInput(sessionView, 0);

      // show modifiers keyboard
      modifiersKeyboardView.setVisibility(View.VISIBLE);
    } else if (showExtendedKeyboard) {
      // hide system keyboard
      mgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
      mgr.hideSoftInputFromWindow(sessionView.getWindowToken(), 0);

      // show extended keyboard
      keyboardView.setKeyboard(specialkeysKeyboard);
      keyboardView.setVisibility(View.VISIBLE);
      modifiersKeyboardView.setVisibility(View.VISIBLE);
    } else {
      // hide both
      mgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
      mgr.hideSoftInputFromWindow(sessionView.getWindowToken(), 0);
      keyboardView.setVisibility(View.GONE);
      modifiersKeyboardView.setVisibility(View.GONE);

      // clear any active key modifiers)
      keyboardMapper.clearlAllModifiers();
    }

    sysKeyboardVisible = showSystemKeyboard;
    extKeyboardVisible = showExtendedKeyboard;
  }

  private void closeSessionActivity(int resultCode) {
    // Go back to home activity (and send intent data back to home)
    setResult(resultCode, getIntent());
    finish();
  }

  // update the state of our modifier keys
  private void updateModifierKeyStates() {
    // check if any key is in the keycodes list

    List<Keyboard.Key> keys = modifiersKeyboard.getKeys();
    for (Iterator<Keyboard.Key> it = keys.iterator(); it.hasNext(); ) {
      // if the key is a sticky key - just set it to off
      Keyboard.Key curKey = it.next();
      if (curKey.sticky) {
        switch (keyboardMapper.getModifierState(curKey.codes[0])) {
          case KeyboardMapper.KEYSTATE_ON:
            curKey.on = true;
            curKey.pressed = false;
            break;

          case KeyboardMapper.KEYSTATE_OFF:
            curKey.on = false;
            curKey.pressed = false;
            break;

          case KeyboardMapper.KEYSTATE_LOCKED:
            curKey.on = true;
            curKey.pressed = true;
            break;
        }
      }
    }

    // refresh image
    modifiersKeyboardView.invalidateAllKeys();
  }

  private void sendDelayedMoveEvent(int x, int y) {
    if (uiHandler.hasMessages(UIHandler.SEND_MOVE_EVENT)) {
      uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT);
      discardedMoveEvents++;
    } else discardedMoveEvents = 0;

    if (discardedMoveEvents > MAX_DISCARDED_MOVE_EVENTS)
      LibFreeRDP.sendCursorEvent(session.getInstance(), x, y, Mouse.getMoveEvent());
    else
      uiHandler.sendMessageDelayed(
          Message.obtain(null, UIHandler.SEND_MOVE_EVENT, x, y), SEND_MOVE_EVENT_TIMEOUT);
  }

  private void cancelDelayedMoveEvent() {
    uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT);
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.session_menu, menu);
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    // refer to http://tools.android.com/tips/non-constant-fields why we can't use switch/case here
    // ..
    int itemId = item.getItemId();

    if (itemId == R.id.session_touch_pointer) {
      // toggle touch pointer
      if (touchPointerView.getVisibility() == View.VISIBLE) {
        touchPointerView.setVisibility(View.INVISIBLE);
        sessionView.setTouchPointerPadding(0, 0);
      } else {
        touchPointerView.setVisibility(View.VISIBLE);
        sessionView.setTouchPointerPadding(
            touchPointerView.getPointerWidth(), touchPointerView.getPointerHeight());
      }
    } else if (itemId == R.id.session_sys_keyboard) {
      showKeyboard(!sysKeyboardVisible, false);
    } else if (itemId == R.id.session_ext_keyboard) {
      showKeyboard(false, !extKeyboardVisible);
    } else if (itemId == R.id.session_disconnect) {
      showKeyboard(false, false);
      LibFreeRDP.disconnect(session.getInstance());
    }

    return true;
  }

  @Override
  public void onBackPressed() {
    // hide keyboards (if any visible) or send alt+f4 to the session
    if (sysKeyboardVisible || extKeyboardVisible) showKeyboard(false, false);
    else keyboardMapper.sendAltF4();
  }

  // android keyboard input handling
  // We always use the unicode value to process input from the android keyboard except if key
  // modifiers
  // (like Win, Alt, Ctrl) are activated. In this case we will send the virtual key code to allow
  // key
  // combinations (like Win + E to open the explorer).
  @Override
  public boolean onKeyDown(int keycode, KeyEvent event) {
    return keyboardMapper.processAndroidKeyEvent(event);
  }

  @Override
  public boolean onKeyUp(int keycode, KeyEvent event) {
    return keyboardMapper.processAndroidKeyEvent(event);
  }

  // onKeyMultiple is called for input of some special characters like umlauts and some symbol
  // characters
  @Override
  public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
    return keyboardMapper.processAndroidKeyEvent(event);
  }

  // ****************************************************************************
  // KeyboardView.KeyboardActionEventListener
  @Override
  public void onKey(int primaryCode, int[] keyCodes) {
    keyboardMapper.processCustomKeyEvent(primaryCode);
  }

  @Override
  public void onText(CharSequence text) {}

  @Override
  public void swipeRight() {}

  @Override
  public void swipeLeft() {}

  @Override
  public void swipeDown() {}

  @Override
  public void swipeUp() {}

  @Override
  public void onPress(int primaryCode) {}

  @Override
  public void onRelease(int primaryCode) {}

  // ****************************************************************************
  // KeyboardMapper.KeyProcessingListener implementation
  @Override
  public void processVirtualKey(int virtualKeyCode, boolean down) {
    LibFreeRDP.sendKeyEvent(session.getInstance(), virtualKeyCode, down);
  }

  @Override
  public void processUnicodeKey(int unicodeKey) {
    LibFreeRDP.sendUnicodeKeyEvent(session.getInstance(), unicodeKey);
  }

  @Override
  public void switchKeyboard(int keyboardType) {
    switch (keyboardType) {
      case KeyboardMapper.KEYBOARD_TYPE_FUNCTIONKEYS:
        keyboardView.setKeyboard(specialkeysKeyboard);
        break;

      case KeyboardMapper.KEYBOARD_TYPE_NUMPAD:
        keyboardView.setKeyboard(numpadKeyboard);
        break;

      case KeyboardMapper.KEYBOARD_TYPE_CURSOR:
        keyboardView.setKeyboard(cursorKeyboard);
        break;

      default:
        break;
    }
  }

  @Override
  public void modifiersChanged() {
    updateModifierKeyStates();
  }

  // ****************************************************************************
  // LibFreeRDP UI event listener implementation
  @Override
  public void OnSettingsChanged(int width, int height, int bpp) {

    if (bpp > 16) bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
    else bitmap = Bitmap.createBitmap(width, height, Config.RGB_565);

    session.setSurface(new BitmapDrawable(bitmap));

    // check this settings and initial settings - if they are not equal the server doesn't support
    // our settings
    // FIXME: the additional check (settings.getWidth() != width + 1) is for the RDVH bug fix to
    // avoid accidental notifications
    // (refer to android_freerdp.c for more info on this problem)
    BookmarkBase.ScreenSettings settings = session.getBookmark().getActiveScreenSettings();
    if ((settings.getWidth() != width && settings.getWidth() != width + 1)
        || settings.getHeight() != height
        || settings.getColors() != bpp)
      uiHandler.sendMessage(
          Message.obtain(
              null,
              UIHandler.DISPLAY_TOAST,
              getResources().getText(R.string.info_capabilities_changed)));
  }

  @Override
  public void OnGraphicsUpdate(int x, int y, int width, int height) {
    LibFreeRDP.updateGraphics(session.getInstance(), bitmap, x, y, width, height);

    sessionView.addInvalidRegion(new Rect(x, y, x + width, y + height));

    /* since sessionView can only be modified from the UI thread
     * any modifications to it need to be scheduled */

    uiHandler.sendEmptyMessage(UIHandler.REFRESH_SESSIONVIEW);
  }

  @Override
  public void OnGraphicsResize(int width, int height, int bpp) {
    // replace bitmap
    if (bpp > 16) bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
    else bitmap = Bitmap.createBitmap(width, height, Config.RGB_565);
    session.setSurface(new BitmapDrawable(bitmap));

    /* since sessionView can only be modified from the UI thread
     * any modifications to it need to be scheduled */
    uiHandler.sendEmptyMessage(UIHandler.GRAPHICS_CHANGED);
  }

  private boolean callbackDialogResult;

  @Override
  public boolean OnAuthenticate(
      StringBuilder username, StringBuilder domain, StringBuilder password) {
    // this is where the return code of our dialog will be stored
    callbackDialogResult = false;

    // set text fields
    ((EditText) userCredView.findViewById(R.id.editTextUsername)).setText(username);
    ((EditText) userCredView.findViewById(R.id.editTextDomain)).setText(domain);
    ((EditText) userCredView.findViewById(R.id.editTextPassword)).setText(password);

    // start dialog in UI thread
    uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG, dlgUserCredentials));

    // wait for result
    try {
      synchronized (dlgUserCredentials) {
        dlgUserCredentials.wait();
      }
    } catch (InterruptedException e) {
    }

    // clear buffers
    username.setLength(0);
    domain.setLength(0);
    password.setLength(0);

    // read back user credentials
    username.append(
        ((EditText) userCredView.findViewById(R.id.editTextUsername)).getText().toString());
    domain.append(((EditText) userCredView.findViewById(R.id.editTextDomain)).getText().toString());
    password.append(
        ((EditText) userCredView.findViewById(R.id.editTextPassword)).getText().toString());

    return callbackDialogResult;
  }

  @Override
  public boolean OnVerifiyCertificate(String subject, String issuer, String fingerprint) {

    // see if global settings says accept all
    if (GlobalSettings.getAcceptAllCertificates()) return true;

    // this is where the return code of our dialog will be stored
    callbackDialogResult = false;

    // set message
    String msg = getResources().getString(R.string.dlg_msg_verify_certificate);
    msg = msg + "\n\nSubject: " + subject + "\nIssuer: " + issuer + "\nFingerprint: " + fingerprint;
    dlgVerifyCertificate.setMessage(msg);

    // start dialog in UI thread
    uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG, dlgVerifyCertificate));

    // wait for result
    try {
      synchronized (dlgVerifyCertificate) {
        dlgVerifyCertificate.wait();
      }
    } catch (InterruptedException e) {
    }

    return callbackDialogResult;
  }

  // ****************************************************************************
  // ScrollView2DListener implementation
  private void resetZoomControlsAutoHideTimeout() {
    uiHandler.removeMessages(UIHandler.HIDE_ZOOMCONTROLS);
    uiHandler.sendEmptyMessageDelayed(UIHandler.HIDE_ZOOMCONTROLS, ZOOMCONTROLS_AUTOHIDE_TIMEOUT);
  }

  @Override
  public void onScrollChanged(ScrollView2D scrollView, int x, int y, int oldx, int oldy) {
    zoomControls.setIsZoomInEnabled(!sessionView.isAtMaxZoom());
    zoomControls.setIsZoomOutEnabled(!sessionView.isAtMinZoom());
    if (!GlobalSettings.getHideZoomControls() && zoomControls.getVisibility() != View.VISIBLE)
      zoomControls.show();
    resetZoomControlsAutoHideTimeout();
  }

  // ****************************************************************************
  // SessionView.SessionViewListener
  @Override
  public void onSessionViewBeginTouch() {
    scrollView.setScrollEnabled(false);
  }

  @Override
  public void onSessionViewEndTouch() {
    scrollView.setScrollEnabled(true);
  }

  @Override
  public void onSessionViewLeftTouch(int x, int y, boolean down) {
    if (!down) cancelDelayedMoveEvent();

    LibFreeRDP.sendCursorEvent(
        session.getInstance(),
        x,
        y,
        toggleMouseButtons ? Mouse.getRightButtonEvent(down) : Mouse.getLeftButtonEvent(down));

    if (!down) toggleMouseButtons = false;
  }

  public void onSessionViewRightTouch(int x, int y, boolean down) {
    if (!down) toggleMouseButtons = !toggleMouseButtons;
  }

  @Override
  public void onSessionViewMove(int x, int y) {
    sendDelayedMoveEvent(x, y);
  }

  @Override
  public void onSessionViewScroll(boolean down) {
    LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0, Mouse.getScrollEvent(down));
  }

  // ****************************************************************************
  // TouchPointerView.TouchPointerListener
  @Override
  public void onTouchPointerClose() {
    touchPointerView.setVisibility(View.INVISIBLE);
    sessionView.setTouchPointerPadding(0, 0);
  }

  private Point mapScreenCoordToSessionCoord(int x, int y) {
    int mappedX = (int) ((float) (x + scrollView.getScrollX()) / sessionView.getZoom());
    int mappedY = (int) ((float) (y + scrollView.getScrollY()) / sessionView.getZoom());
    if (mappedX > bitmap.getWidth()) mappedX = bitmap.getWidth();
    if (mappedY > bitmap.getHeight()) mappedY = bitmap.getHeight();
    return new Point(mappedX, mappedY);
  }

  @Override
  public void onTouchPointerLeftClick(int x, int y, boolean down) {
    Point p = mapScreenCoordToSessionCoord(x, y);
    LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y, Mouse.getLeftButtonEvent(down));
  }

  @Override
  public void onTouchPointerRightClick(int x, int y, boolean down) {
    Point p = mapScreenCoordToSessionCoord(x, y);
    LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y, Mouse.getRightButtonEvent(down));
  }

  @Override
  public void onTouchPointerMove(int x, int y) {
    Point p = mapScreenCoordToSessionCoord(x, y);
    LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y, Mouse.getMoveEvent());

    if (autoScrollTouchPointer && !uiHandler.hasMessages(UIHandler.SCROLLING_REQUESTED)) {
      Log.v(TAG, "Starting auto-scroll");
      uiHandler.sendEmptyMessageDelayed(UIHandler.SCROLLING_REQUESTED, SCROLLING_TIMEOUT);
    }
  }

  @Override
  public void onTouchPointerScroll(boolean down) {
    LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0, Mouse.getScrollEvent(down));
  }

  @Override
  public void onTouchPointerToggleKeyboard() {
    showKeyboard(!sysKeyboardVisible, false);
  }

  @Override
  public void onTouchPointerToggleExtKeyboard() {
    showKeyboard(false, !extKeyboardVisible);
  }

  @Override
  public void onTouchPointerResetScrollZoom() {
    sessionView.setZoom(1.0f);
    scrollView.scrollTo(0, 0);
  }
}
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // show status bar or make fullscreen?
    if (GlobalSettings.getHideStatusBar())
      getWindow()
          .setFlags(
              WindowManager.LayoutParams.FLAG_FULLSCREEN,
              WindowManager.LayoutParams.FLAG_FULLSCREEN);
    this.setContentView(R.layout.session);

    Log.v(TAG, "Session.onCreate");

    // ATTENTION: We use the onGlobalLayout notification to start our session.
    // This is because only then we can know the exact size of our session when using fit screen
    // accounting for any status bars etc. that Android might throws on us. A bit weird looking
    // but this is the only way ...
    final View activityRootView = findViewById(R.id.session_root_view);
    activityRootView
        .getViewTreeObserver()
        .addOnGlobalLayoutListener(
            new OnGlobalLayoutListener() {
              @Override
              public void onGlobalLayout() {
                screen_width = activityRootView.getWidth();
                screen_height = activityRootView.getHeight();

                // start session
                if (!sessionRunning && getIntent() != null) {
                  processIntent(getIntent());
                  sessionRunning = true;
                }
              }
            });

    sessionView = (SessionView) findViewById(R.id.sessionView);
    sessionView.setScaleGestureDetector(new ScaleGestureDetector(this, new PinchZoomListener()));
    sessionView.setSessionViewListener(this);
    sessionView.requestFocus();

    touchPointerView = (TouchPointerView) findViewById(R.id.touchPointerView);
    touchPointerView.setTouchPointerListener(this);

    keyboardMapper = new KeyboardMapper();
    keyboardMapper.init(this);

    modifiersKeyboard = new Keyboard(getApplicationContext(), R.xml.modifiers_keyboard);
    specialkeysKeyboard = new Keyboard(getApplicationContext(), R.xml.specialkeys_keyboard);
    numpadKeyboard = new Keyboard(getApplicationContext(), R.xml.numpad_keyboard);
    cursorKeyboard = new Keyboard(getApplicationContext(), R.xml.cursor_keyboard);

    // hide keyboard below the sessionView
    keyboardView = (KeyboardView) findViewById(R.id.extended_keyboard);
    keyboardView.setKeyboard(specialkeysKeyboard);
    keyboardView.setOnKeyboardActionListener(this);

    modifiersKeyboardView = (KeyboardView) findViewById(R.id.extended_keyboard_header);
    modifiersKeyboardView.setKeyboard(modifiersKeyboard);
    modifiersKeyboardView.setOnKeyboardActionListener(this);

    scrollView = (ScrollView2D) findViewById(R.id.sessionScrollView);
    scrollView.setScrollViewListener(this);
    uiHandler = new UIHandler();
    libFreeRDPBroadcastReceiver = new LibFreeRDPBroadcastReceiver();

    zoomControls = (ZoomControls) findViewById(R.id.zoomControls);
    zoomControls.hide();
    zoomControls.setOnZoomInClickListener(
        new View.OnClickListener() {
          @Override
          public void onClick(View v) {
            resetZoomControlsAutoHideTimeout();
            zoomControls.setIsZoomInEnabled(sessionView.zoomIn(ZOOMING_STEP));
            zoomControls.setIsZoomOutEnabled(true);
          }
        });
    zoomControls.setOnZoomOutClickListener(
        new View.OnClickListener() {
          @Override
          public void onClick(View v) {
            resetZoomControlsAutoHideTimeout();
            zoomControls.setIsZoomOutEnabled(sessionView.zoomOut(ZOOMING_STEP));
            zoomControls.setIsZoomInEnabled(true);
          }
        });

    toggleMouseButtons = false;

    createDialogs();

    // register freerdp events broadcast receiver
    IntentFilter filter = new IntentFilter();
    filter.addAction(GlobalApp.ACTION_EVENT_FREERDP);
    registerReceiver(libFreeRDPBroadcastReceiver, filter);
  }