/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.android_scripting.facade.ui;

import android.app.ProgressDialog;
import android.app.Service;
import android.util.AndroidRuntimeException;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;

import com.googlecode.android_scripting.BaseApplication;
import com.googlecode.android_scripting.FutureActivityTaskExecutor;
import com.googlecode.android_scripting.Log;
import com.googlecode.android_scripting.facade.EventFacade;
import com.googlecode.android_scripting.facade.FacadeManager;
import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
import com.googlecode.android_scripting.rpc.Rpc;
import com.googlecode.android_scripting.rpc.RpcDefault;
import com.googlecode.android_scripting.rpc.RpcOptional;
import com.googlecode.android_scripting.rpc.RpcParameter;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

import org.json.JSONArray;
import org.json.JSONException;

/**
 * User Interface Facade. <br>
 * <br>
 * <b>Usage Notes</b><br>
 * <br>
 * The UI facade provides access to a selection of dialog boxes for general user interaction, and
 * also hosts the {@link #webViewShow} call which allows interactive use of html pages.<br>
 * The general use of the dialog functions is as follows:<br>
 * <ol>
 * <li>Create a dialog using one of the following calls:
 * <ul>
 * <li>{@link #dialogCreateInput}
 * <li>{@link #dialogCreateAlert}
 * <li>{@link #dialogCreateDatePicker}
 * <li>{@link #dialogCreateHorizontalProgress}
 * <li>{@link #dialogCreatePassword}
 * <li>{@link #dialogCreateSeekBar}
 * <li>{@link #dialogCreateSpinnerProgress}
 * </ul>
 * <li>Set additional features to your dialog
 * <ul>
 * <li>{@link #dialogSetItems} Set a list of items. Used like a menu.
 * <li>{@link #dialogSetMultiChoiceItems} Set a multichoice list of items.
 * <li>{@link #dialogSetSingleChoiceItems} Set a single choice list of items.
 * <li>{@link #dialogSetPositiveButtonText}
 * <li>{@link #dialogSetNeutralButtonText}
 * <li>{@link #dialogSetNegativeButtonText}
 * <li>{@link #dialogSetMaxProgress} Set max progress for your progress bar.
 * </ul>
 * <li>Display the dialog using {@link #dialogShow}
 * <li>Update dialog information if needed
 * <ul>
 * <li>{@link #dialogSetCurrentProgress}
 * </ul>
 * <li>Get the results
 * <ul>
 * <li>Using {@link #dialogGetResponse}, which will wait until the user performs an action to close
 * the dialog box, or
 * <li>Use eventPoll to wait for a "dialog" event.
 * <li>You can find out which list items were selected using {@link #dialogGetSelectedItems}, which
 * returns an array of numeric indices to your list. For a single choice list, there will only ever
 * be one of these.
 * </ul>
 * <li>Once done, use {@link #dialogDismiss} to remove the dialog.
 * </ol>
 * <br>
 * You can also manipulate menu options. The menu options are available for both {@link #dialogShow}
 * and {@link #fullShow}.
 * <ul>
 * <li>{@link #clearOptionsMenu}
 * <li>{@link #addOptionsMenuItem}
 * </ul>
 * <br>
 * <b>Some notes:</b><br>
 * Not every dialogSet function is relevant to every dialog type, ie, dialogSetMaxProgress obviously
 * only applies to dialogs created with a progress bar. Also, an Alert Dialog may have a message or
 * items, not both. If you set both, items will take priority.<br>
 * In addition to the above functions, {@link #dialogGetInput} and {@link #dialogGetPassword} are
 * convenience functions that create, display and return the relevant dialogs in one call.<br>
 * There is only ever one instance of a dialog. Any dialogCreate call will cause the existing dialog
 * to be destroyed.
 *
 */
public class UiFacade extends RpcReceiver {
  // This value should not be used for menu groups outside this class.
  private static final int MENU_GROUP_ID = Integer.MAX_VALUE;
  private static final String blankLayout = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
          + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\""
          + "android:id=\"@+id/background\" android:orientation=\"vertical\""
          + "android:layout_width=\"match_parent\" android:layout_height=\"match_parent\""
          + "android:background=\"#ff000000\"></LinearLayout>";

  private final Service mService;
  private final FutureActivityTaskExecutor mTaskQueue;
  private DialogTask mDialogTask;
  private FullScreenTask mFullScreenTask;

  private final List<UiMenuItem> mContextMenuItems;
  private final List<UiMenuItem> mOptionsMenuItems;
  private final AtomicBoolean mMenuUpdated;

  private final EventFacade mEventFacade;
  private List<Integer> mOverrideKeys = Collections.synchronizedList(new ArrayList<Integer>());

  private float mLastXPosition;

  public UiFacade(FacadeManager manager) {
    super(manager);
    mService = manager.getService();
    mTaskQueue = ((BaseApplication) mService.getApplication()).getTaskExecutor();
    mContextMenuItems = new CopyOnWriteArrayList<UiMenuItem>();
    mOptionsMenuItems = new CopyOnWriteArrayList<UiMenuItem>();
    mEventFacade = manager.getReceiver(EventFacade.class);
    mMenuUpdated = new AtomicBoolean(false);
  }

  /**
   * For inputType, see <a
   * href="http://developer.android.com/reference/android/R.styleable.html#TextView_inputType"
   * >InputTypes</a>. Some useful ones are text, number, and textUri. Multiple flags can be
   * supplied, seperated by "|", ie: "textUri|textAutoComplete"
   */
  @Rpc(description = "Create a text input dialog.")
  public void dialogCreateInput(
      @RpcParameter(name = "title", description = "title of the input box") @RpcDefault("Value") final String title,
      @RpcParameter(name = "message", description = "message to display above the input box") @RpcDefault("Please enter value:") final String message,
      @RpcParameter(name = "defaultText", description = "text to insert into the input box") @RpcOptional final String text,
      @RpcParameter(name = "inputType", description = "type of input data, ie number or text") @RpcOptional final String inputType)
      throws InterruptedException {
    dialogDismiss();
    mDialogTask = new AlertDialogTask(title, message);
    ((AlertDialogTask) mDialogTask).setTextInput(text);
    if (inputType != null) {
      ((AlertDialogTask) mDialogTask).setEditInputType(inputType);
    }
  }

  @Rpc(description = "Create a password input dialog.")
  public void dialogCreatePassword(
      @RpcParameter(name = "title", description = "title of the input box") @RpcDefault("Password") final String title,
      @RpcParameter(name = "message", description = "message to display above the input box") @RpcDefault("Please enter password:") final String message) {
    dialogDismiss();
    mDialogTask = new AlertDialogTask(title, message);
    ((AlertDialogTask) mDialogTask).setPasswordInput();
  }

  /**
   * The result is the user's input, or None (null) if cancel was hit. <br>
   * Example (python)
   *
   * <pre>
   * import android
   * droid=android.Android()
   *
   * print droid.dialogGetInput("Title","Message","Default").result
   * </pre>
   *
   */
  @SuppressWarnings("unchecked")
  @Rpc(description = "Queries the user for a text input.")
  public String dialogGetInput(
      @RpcParameter(name = "title", description = "title of the input box") @RpcDefault("Value") final String title,
      @RpcParameter(name = "message", description = "message to display above the input box") @RpcDefault("Please enter value:") final String message,
      @RpcParameter(name = "defaultText", description = "text to insert into the input box") @RpcOptional final String text)
      throws InterruptedException {
    dialogCreateInput(title, message, text, "text");
    dialogSetNegativeButtonText("Cancel");
    dialogSetPositiveButtonText("Ok");
    dialogShow();
    Map<String, Object> response = (Map<String, Object>) dialogGetResponse();
    if ("positive".equals(response.get("which"))) {
      return (String) response.get("value");
    } else {
      return null;
    }
  }

  @SuppressWarnings("unchecked")
  @Rpc(description = "Queries the user for a password.")
  public String dialogGetPassword(
      @RpcParameter(name = "title", description = "title of the password box") @RpcDefault("Password") final String title,
      @RpcParameter(name = "message", description = "message to display above the input box") @RpcDefault("Please enter password:") final String message)
      throws InterruptedException {
    dialogCreatePassword(title, message);
    dialogSetNegativeButtonText("Cancel");
    dialogSetPositiveButtonText("Ok");
    dialogShow();
    Map<String, Object> response = (Map<String, Object>) dialogGetResponse();
    if ("positive".equals(response.get("which"))) {
      return (String) response.get("value");
    } else {
      return null;
    }
  }

  @Rpc(description = "Create a spinner progress dialog.")
  public void dialogCreateSpinnerProgress(@RpcParameter(name = "title") @RpcOptional String title,
      @RpcParameter(name = "message") @RpcOptional String message,
      @RpcParameter(name = "maximum progress") @RpcDefault("100") Integer max) {
    dialogDismiss(); // Dismiss any existing dialog.
    mDialogTask = new ProgressDialogTask(ProgressDialog.STYLE_SPINNER, max, title, message, true);
  }

  @Rpc(description = "Create a horizontal progress dialog.")
  public void dialogCreateHorizontalProgress(
      @RpcParameter(name = "title") @RpcOptional String title,
      @RpcParameter(name = "message") @RpcOptional String message,
      @RpcParameter(name = "maximum progress") @RpcDefault("100") Integer max) {
    dialogDismiss(); // Dismiss any existing dialog.
    mDialogTask =
        new ProgressDialogTask(ProgressDialog.STYLE_HORIZONTAL, max, title, message, true);
  }

  /**
   * <b>Example (python)</b>
   *
   * <pre>
   *   import android
   *   droid=android.Android()
   *   droid.dialogCreateAlert("I like swords.","Do you like swords?")
   *   droid.dialogSetPositiveButtonText("Yes")
   *   droid.dialogSetNegativeButtonText("No")
   *   droid.dialogShow()
   *   response=droid.dialogGetResponse().result
   *   droid.dialogDismiss()
   *   if response.has_key("which"):
   *     result=response["which"]
   *     if result=="positive":
   *       print "Yay! I like swords too!"
   *     elif result=="negative":
   *       print "Oh. How sad."
   *   elif response.has_key("canceled"): # Yes, I know it's mispelled.
   *     print "You can't even make up your mind?"
   *   else:
   *     print "Unknown response=",response
   *
   *   print "Done"
   * </pre>
   */
  @Rpc(description = "Create alert dialog.")
  public void dialogCreateAlert(@RpcParameter(name = "title") @RpcOptional String title,
      @RpcParameter(name = "message") @RpcOptional String message) {
    dialogDismiss(); // Dismiss any existing dialog.
    mDialogTask = new AlertDialogTask(title, message);
  }

  /**
   * Will produce "dialog" events on change, containing:
   * <ul>
   * <li>"progress" - Position chosen, between 0 and max
   * <li>"which" = "seekbar"
   * <li>"fromuser" = true/false change is from user input
   * </ul>
   * Response will contain a "progress" element.
   */
  @Rpc(description = "Create seek bar dialog.")
  public void dialogCreateSeekBar(
      @RpcParameter(name = "starting value") @RpcDefault("50") Integer progress,
      @RpcParameter(name = "maximum value") @RpcDefault("100") Integer max,
      @RpcParameter(name = "title") String title, @RpcParameter(name = "message") String message) {
    dialogDismiss(); // Dismiss any existing dialog.
    mDialogTask = new SeekBarDialogTask(progress, max, title, message);
  }

  @Rpc(description = "Create time picker dialog.")
  public void dialogCreateTimePicker(
      @RpcParameter(name = "hour") @RpcDefault("0") Integer hour,
      @RpcParameter(name = "minute") @RpcDefault("0") Integer minute,
      @RpcParameter(name = "is24hour", description = "Use 24 hour clock") @RpcDefault("false") Boolean is24hour) {
    dialogDismiss(); // Dismiss any existing dialog.
    mDialogTask = new TimePickerDialogTask(hour, minute, is24hour);
  }

  @Rpc(description = "Create date picker dialog.")
  public void dialogCreateDatePicker(@RpcParameter(name = "year") @RpcDefault("1970") Integer year,
      @RpcParameter(name = "month") @RpcDefault("1") Integer month,
      @RpcParameter(name = "day") @RpcDefault("1") Integer day) {
    dialogDismiss(); // Dismiss any existing dialog.
    mDialogTask = new DatePickerDialogTask(year, month, day);
  }

  @Rpc(description = "Dismiss dialog.")
  public void dialogDismiss() {
    if (mDialogTask != null) {
      mDialogTask.dismissDialog();
      mDialogTask = null;
    }
  }

  @Rpc(description = "Show dialog.")
  public void dialogShow() throws InterruptedException {
    if (mDialogTask != null && mDialogTask.getDialog() == null) {
      mDialogTask.setEventFacade(mEventFacade);
      mTaskQueue.execute(mDialogTask);
      mDialogTask.getShowLatch().await();
    } else {
      throw new RuntimeException("No dialog to show.");
    }
  }

  @Rpc(description = "Set progress dialog current value.")
  public void dialogSetCurrentProgress(@RpcParameter(name = "current") Integer current) {
    if (mDialogTask != null && mDialogTask instanceof ProgressDialogTask) {
      ((ProgressDialog) mDialogTask.getDialog()).setProgress(current);
    } else {
      throw new RuntimeException("No valid dialog to assign value to.");
    }
  }

  @Rpc(description = "Set progress dialog maximum value.")
  public void dialogSetMaxProgress(@RpcParameter(name = "max") Integer max) {
    if (mDialogTask != null && mDialogTask instanceof ProgressDialogTask) {
      ((ProgressDialog) mDialogTask.getDialog()).setMax(max);
    } else {
      throw new RuntimeException("No valid dialog to set maximum value of.");
    }
  }

  @Rpc(description = "Set alert dialog positive button text.")
  public void dialogSetPositiveButtonText(@RpcParameter(name = "text") String text) {
    if (mDialogTask != null && mDialogTask instanceof AlertDialogTask) {
      ((AlertDialogTask) mDialogTask).setPositiveButtonText(text);
    } else if (mDialogTask != null && mDialogTask instanceof SeekBarDialogTask) {
      ((SeekBarDialogTask) mDialogTask).setPositiveButtonText(text);
    } else {
      throw new AndroidRuntimeException("No dialog to add button to.");
    }
  }

  @Rpc(description = "Set alert dialog button text.")
  public void dialogSetNegativeButtonText(@RpcParameter(name = "text") String text) {
    if (mDialogTask != null && mDialogTask instanceof AlertDialogTask) {
      ((AlertDialogTask) mDialogTask).setNegativeButtonText(text);
    } else if (mDialogTask != null && mDialogTask instanceof SeekBarDialogTask) {
      ((SeekBarDialogTask) mDialogTask).setNegativeButtonText(text);
    } else {
      throw new AndroidRuntimeException("No dialog to add button to.");
    }
  }

  @Rpc(description = "Set alert dialog button text.")
  public void dialogSetNeutralButtonText(@RpcParameter(name = "text") String text) {
    if (mDialogTask != null && mDialogTask instanceof AlertDialogTask) {
      ((AlertDialogTask) mDialogTask).setNeutralButtonText(text);
    } else {
      throw new AndroidRuntimeException("No dialog to add button to.");
    }
  }

  // TODO(damonkohler): Make RPC layer translate between JSONArray and List<Object>.
  /**
   * This effectively creates list of options. Clicking on an item will immediately return an "item"
   * element, which is the index of the selected item.
   */
  @Rpc(description = "Set alert dialog list items.")
  public void dialogSetItems(@RpcParameter(name = "items") JSONArray items) {
    if (mDialogTask != null && mDialogTask instanceof AlertDialogTask) {
      ((AlertDialogTask) mDialogTask).setItems(items);
    } else {
      throw new AndroidRuntimeException("No dialog to add list to.");
    }
  }

  /**
   * This creates a list of radio buttons. You can select one item out of the list. A response will
   * not be returned until the dialog is closed, either with the Cancel key or a button
   * (positive/negative/neutral). Use {@link #dialogGetSelectedItems()} to find out what was
   * selected.
   */
  @Rpc(description = "Set dialog single choice items and selected item.")
  public void dialogSetSingleChoiceItems(
      @RpcParameter(name = "items") JSONArray items,
      @RpcParameter(name = "selected", description = "selected item index") @RpcDefault("0") Integer selected) {
    if (mDialogTask != null && mDialogTask instanceof AlertDialogTask) {
      ((AlertDialogTask) mDialogTask).setSingleChoiceItems(items, selected);
    } else {
      throw new AndroidRuntimeException("No dialog to add list to.");
    }
  }

  /**
   * This creates a list of check boxes. You can select multiple items out of the list. A response
   * will not be returned until the dialog is closed, either with the Cancel key or a button
   * (positive/negative/neutral). Use {@link #dialogGetSelectedItems()} to find out what was
   * selected.
   */

  @Rpc(description = "Set dialog multiple choice items and selection.")
  public void dialogSetMultiChoiceItems(
      @RpcParameter(name = "items") JSONArray items,
      @RpcParameter(name = "selected", description = "list of selected items") @RpcOptional JSONArray selected)
      throws JSONException {
    if (mDialogTask != null && mDialogTask instanceof AlertDialogTask) {
      ((AlertDialogTask) mDialogTask).setMultiChoiceItems(items, selected);
    } else {
      throw new AndroidRuntimeException("No dialog to add list to.");
    }
  }

  @Rpc(description = "Returns dialog response.")
  public Object dialogGetResponse() {
    try {
      return mDialogTask.getResult();
    } catch (Exception e) {
      throw new AndroidRuntimeException(e);
    }
  }

  @Rpc(description = "This method provides list of items user selected.", returns = "Selected items")
  public Set<Integer> dialogGetSelectedItems() {
    if (mDialogTask != null && mDialogTask instanceof AlertDialogTask) {
      return ((AlertDialogTask) mDialogTask).getSelectedItems();
    } else {
      throw new AndroidRuntimeException("No dialog to add list to.");
    }
  }

  @Rpc(description = "Adds a new item to context menu.")
  public void addContextMenuItem(
      @RpcParameter(name = "label", description = "label for this menu item") String label,
      @RpcParameter(name = "event", description = "event that will be generated on menu item click") String event,
      @RpcParameter(name = "eventData") @RpcOptional Object data) {
    mContextMenuItems.add(new UiMenuItem(label, event, data, null));
  }

  /**
   * <b>Example (python)</b>
   *
   * <pre>
   * import android
   * droid=android.Android()
   *
   * droid.addOptionsMenuItem("Silly","silly",None,"star_on")
   * droid.addOptionsMenuItem("Sensible","sensible","I bet.","star_off")
   * droid.addOptionsMenuItem("Off","off",None,"ic_menu_revert")
   *
   * print "Hit menu to see extra options."
   * print "Will timeout in 10 seconds if you hit nothing."
   *
   * while True: # Wait for events from the menu.
   *   response=droid.eventWait(10000).result
   *   if response==None:
   *     break
   *   print response
   *   if response["name"]=="off":
   *     break
   * print "And done."
   *
   * </pre>
   */
  @Rpc(description = "Adds a new item to options menu.")
  public void addOptionsMenuItem(
      @RpcParameter(name = "label", description = "label for this menu item") String label,
      @RpcParameter(name = "event", description = "event that will be generated on menu item click") String event,
      @RpcParameter(name = "eventData") @RpcOptional Object data,
      @RpcParameter(name = "iconName", description = "Android system menu icon, see http://developer.android.com/reference/android/R.drawable.html") @RpcOptional String iconName) {
    mOptionsMenuItems.add(new UiMenuItem(label, event, data, iconName));
    mMenuUpdated.set(true);
  }

  @Rpc(description = "Removes all items previously added to context menu.")
  public void clearContextMenu() {
    mContextMenuItems.clear();
  }

  @Rpc(description = "Removes all items previously added to options menu.")
  public void clearOptionsMenu() {
    mOptionsMenuItems.clear();
    mMenuUpdated.set(true);
  }

  public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
    for (UiMenuItem item : mContextMenuItems) {
      MenuItem menuItem = menu.add(item.mmTitle);
      menuItem.setOnMenuItemClickListener(item.mmListener);
    }
  }

  public boolean onPrepareOptionsMenu(Menu menu) {
    if (mMenuUpdated.getAndSet(false)) {
      menu.removeGroup(MENU_GROUP_ID);
      for (UiMenuItem item : mOptionsMenuItems) {
        MenuItem menuItem = menu.add(MENU_GROUP_ID, Menu.NONE, Menu.NONE, item.mmTitle);
        if (item.mmIcon != null) {
          menuItem.setIcon(mService.getResources()
              .getIdentifier(item.mmIcon, "drawable", "android"));
        }
        menuItem.setOnMenuItemClickListener(item.mmListener);
      }
      return true;
    }
    return true;
  }

  /**
   * See <a href=http://code.google.com/p/android-scripting/wiki/FullScreenUI>wiki page</a> for more
   * detail.
   */
  @Rpc(description = "Show Full Screen.")
  public List<String> fullShow(
      @RpcParameter(name = "layout", description = "String containing View layout") String layout,
      @RpcParameter(name = "title", description = "Activity Title") @RpcOptional String title)
      throws InterruptedException {
    if (mFullScreenTask != null) {
      // fullDismiss();
      mFullScreenTask.setLayout(layout);
      if (title != null) {
        mFullScreenTask.setTitle(title);
      }
    } else {
      mFullScreenTask = new FullScreenTask(layout, title);
      mFullScreenTask.setEventFacade(mEventFacade);
      mFullScreenTask.setUiFacade(this);
      mFullScreenTask.setOverrideKeys(mOverrideKeys);
      mTaskQueue.execute(mFullScreenTask);
      mFullScreenTask.getShowLatch().await();
    }
    return mFullScreenTask.mInflater.getErrors();
  }

  @Rpc(description = "Dismiss Full Screen.")
  public void fullDismiss() {
    if (mFullScreenTask != null) {
      mFullScreenTask.finish();
      mFullScreenTask = null;
    }
  }

  class MouseMotionListener implements View.OnGenericMotionListener {

      @Override
      public boolean onGenericMotion(View v, MotionEvent event) {
          Log.d("Generic motion triggered.");
          if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
              mLastXPosition = event.getAxisValue(MotionEvent.AXIS_X);
              Log.d("New mouse x coord: " + mLastXPosition);
//              Bundle msg = new Bundle();
//              msg.putFloat("value", mLastXPosition);
//              mEventFacade.postEvent("MouseXPositionUpdate", msg);
              return true;
          }
          return false;
      }
  }

  @Rpc(description = "Get Fullscreen Properties")
  public Map<String, Map<String, String>> fullQuery() {
    if (mFullScreenTask == null) {
      throw new RuntimeException("No screen displayed.");
    }
    return mFullScreenTask.getViewAsMap();
  }

  @Rpc(description = "Get fullscreen properties for a specific widget")
  public Map<String, String> fullQueryDetail(
      @RpcParameter(name = "id", description = "id of layout widget") String id) {
    if (mFullScreenTask == null) {
      throw new RuntimeException("No screen displayed.");
    }
    return mFullScreenTask.getViewDetail(id);
  }

  @Rpc(description = "Set fullscreen widget property")
  public String fullSetProperty(
      @RpcParameter(name = "id", description = "id of layout widget") String id,
      @RpcParameter(name = "property", description = "name of property to set") String property,
      @RpcParameter(name = "value", description = "value to set property to") String value) {
    if (mFullScreenTask == null) {
      throw new RuntimeException("No screen displayed.");
    }
    return mFullScreenTask.setViewProperty(id, property, value);
  }

  @Rpc(description = "Attach a list to a fullscreen widget")
  public String fullSetList(
      @RpcParameter(name = "id", description = "id of layout widget") String id,
      @RpcParameter(name = "list", description = "List to set") JSONArray items) {
    if (mFullScreenTask == null) {
      throw new RuntimeException("No screen displayed.");
    }
    return mFullScreenTask.setList(id, items);
  }

  @Rpc(description = "Set the Full Screen Activity Title")
  public void fullSetTitle(
      @RpcParameter(name = "title", description = "Activity Title") String title) {
    if (mFullScreenTask == null) {
      throw new RuntimeException("No screen displayed.");
    }
    mFullScreenTask.setTitle(title);
  }

  /**
   * This will override the default behaviour of keys while in the fullscreen mode. ie:
   *
   * <pre>
   *   droid.fullKeyOverride([24,25],True)
   * </pre>
   *
   * This will override the default behaviour of the volume keys (codes 24 and 25) so that they do
   * not actually adjust the volume. <br>
   * Returns a list of currently overridden keycodes.
   */
  @Rpc(description = "Override default key actions")
  public JSONArray fullKeyOverride(
      @RpcParameter(name = "keycodes", description = "List of keycodes to override") JSONArray keycodes,
      @RpcParameter(name = "enable", description = "Turn overriding or off") @RpcDefault(value = "true") Boolean enable)
      throws JSONException {
    for (int i = 0; i < keycodes.length(); i++) {
      int value = (int) keycodes.getLong(i);
      if (value > 0) {
        if (enable) {
          if (!mOverrideKeys.contains(value)) {
            mOverrideKeys.add(value);
          }
        } else {
          int index = mOverrideKeys.indexOf(value);
          if (index >= 0) {
            mOverrideKeys.remove(index);
          }
        }
      }
    }
    if (mFullScreenTask != null) {
      mFullScreenTask.setOverrideKeys(mOverrideKeys);
    }
    return new JSONArray(mOverrideKeys);
  }

  @Rpc(description = "Start tracking mouse cursor x coordinate.")
  public void startTrackingMouseXCoord() throws InterruptedException {
    View.OnGenericMotionListener l = new MouseMotionListener();
    fullShow(blankLayout, "Blank");
    mFullScreenTask.mView.setOnGenericMotionListener(l);
  }

  @Rpc(description = "Stop tracking mouse cursor x coordinate.")
  public void stopTrackingMouseXCoord() throws InterruptedException {
    fullDismiss();
  }

  @Rpc(description = "Return the latest X position of mouse cursor.")
  public float getLatestMouseXCoord() {
      return mLastXPosition;
  }

@Override
  public void shutdown() {
    fullDismiss();
  }

  private class UiMenuItem {

    private final String mmTitle;
    private final String mmEvent;
    private final Object mmEventData;
    private final String mmIcon;
    private final MenuItem.OnMenuItemClickListener mmListener;

    public UiMenuItem(String title, String event, Object data, String icon) {
      mmTitle = title;
      mmEvent = event;
      mmEventData = data;
      mmIcon = icon;
      mmListener = new MenuItem.OnMenuItemClickListener() {
        @Override
        public boolean onMenuItemClick(MenuItem item) {
          // TODO(damonkohler): Does mmEventData need to be cloned somehow?
          mEventFacade.postEvent(mmEvent, mmEventData);
          return true;
        }
      };
    }
  }
}
