Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.googlecode.android_scripting.facade.ui;
     18 
     19 import android.app.ProgressDialog;
     20 import android.app.Service;
     21 import android.util.AndroidRuntimeException;
     22 import android.view.ContextMenu;
     23 import android.view.ContextMenu.ContextMenuInfo;
     24 import android.view.Menu;
     25 import android.view.MenuItem;
     26 import android.view.MotionEvent;
     27 import android.view.View;
     28 
     29 import com.googlecode.android_scripting.BaseApplication;
     30 import com.googlecode.android_scripting.FutureActivityTaskExecutor;
     31 import com.googlecode.android_scripting.Log;
     32 import com.googlecode.android_scripting.facade.EventFacade;
     33 import com.googlecode.android_scripting.facade.FacadeManager;
     34 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
     35 import com.googlecode.android_scripting.rpc.Rpc;
     36 import com.googlecode.android_scripting.rpc.RpcDefault;
     37 import com.googlecode.android_scripting.rpc.RpcOptional;
     38 import com.googlecode.android_scripting.rpc.RpcParameter;
     39 
     40 import java.util.ArrayList;
     41 import java.util.Collections;
     42 import java.util.List;
     43 import java.util.Map;
     44 import java.util.Set;
     45 import java.util.concurrent.CopyOnWriteArrayList;
     46 import java.util.concurrent.atomic.AtomicBoolean;
     47 
     48 import org.json.JSONArray;
     49 import org.json.JSONException;
     50 
     51 /**
     52  * User Interface Facade. <br>
     53  * <br>
     54  * <b>Usage Notes</b><br>
     55  * <br>
     56  * The UI facade provides access to a selection of dialog boxes for general user interaction, and
     57  * also hosts the {@link #webViewShow} call which allows interactive use of html pages.<br>
     58  * The general use of the dialog functions is as follows:<br>
     59  * <ol>
     60  * <li>Create a dialog using one of the following calls:
     61  * <ul>
     62  * <li>{@link #dialogCreateInput}
     63  * <li>{@link #dialogCreateAlert}
     64  * <li>{@link #dialogCreateDatePicker}
     65  * <li>{@link #dialogCreateHorizontalProgress}
     66  * <li>{@link #dialogCreatePassword}
     67  * <li>{@link #dialogCreateSeekBar}
     68  * <li>{@link #dialogCreateSpinnerProgress}
     69  * </ul>
     70  * <li>Set additional features to your dialog
     71  * <ul>
     72  * <li>{@link #dialogSetItems} Set a list of items. Used like a menu.
     73  * <li>{@link #dialogSetMultiChoiceItems} Set a multichoice list of items.
     74  * <li>{@link #dialogSetSingleChoiceItems} Set a single choice list of items.
     75  * <li>{@link #dialogSetPositiveButtonText}
     76  * <li>{@link #dialogSetNeutralButtonText}
     77  * <li>{@link #dialogSetNegativeButtonText}
     78  * <li>{@link #dialogSetMaxProgress} Set max progress for your progress bar.
     79  * </ul>
     80  * <li>Display the dialog using {@link #dialogShow}
     81  * <li>Update dialog information if needed
     82  * <ul>
     83  * <li>{@link #dialogSetCurrentProgress}
     84  * </ul>
     85  * <li>Get the results
     86  * <ul>
     87  * <li>Using {@link #dialogGetResponse}, which will wait until the user performs an action to close
     88  * the dialog box, or
     89  * <li>Use eventPoll to wait for a "dialog" event.
     90  * <li>You can find out which list items were selected using {@link #dialogGetSelectedItems}, which
     91  * returns an array of numeric indices to your list. For a single choice list, there will only ever
     92  * be one of these.
     93  * </ul>
     94  * <li>Once done, use {@link #dialogDismiss} to remove the dialog.
     95  * </ol>
     96  * <br>
     97  * You can also manipulate menu options. The menu options are available for both {@link #dialogShow}
     98  * and {@link #fullShow}.
     99  * <ul>
    100  * <li>{@link #clearOptionsMenu}
    101  * <li>{@link #addOptionsMenuItem}
    102  * </ul>
    103  * <br>
    104  * <b>Some notes:</b><br>
    105  * Not every dialogSet function is relevant to every dialog type, ie, dialogSetMaxProgress obviously
    106  * only applies to dialogs created with a progress bar. Also, an Alert Dialog may have a message or
    107  * items, not both. If you set both, items will take priority.<br>
    108  * In addition to the above functions, {@link #dialogGetInput} and {@link #dialogGetPassword} are
    109  * convenience functions that create, display and return the relevant dialogs in one call.<br>
    110  * There is only ever one instance of a dialog. Any dialogCreate call will cause the existing dialog
    111  * to be destroyed.
    112  *
    113  */
    114 public class UiFacade extends RpcReceiver {
    115   // This value should not be used for menu groups outside this class.
    116   private static final int MENU_GROUP_ID = Integer.MAX_VALUE;
    117   private static final String blankLayout = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
    118           + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\""
    119           + "android:id=\"@+id/background\" android:orientation=\"vertical\""
    120           + "android:layout_width=\"match_parent\" android:layout_height=\"match_parent\""
    121           + "android:background=\"#ff000000\"></LinearLayout>";
    122 
    123   private final Service mService;
    124   private final FutureActivityTaskExecutor mTaskQueue;
    125   private DialogTask mDialogTask;
    126   private FullScreenTask mFullScreenTask;
    127 
    128   private final List<UiMenuItem> mContextMenuItems;
    129   private final List<UiMenuItem> mOptionsMenuItems;
    130   private final AtomicBoolean mMenuUpdated;
    131 
    132   private final EventFacade mEventFacade;
    133   private List<Integer> mOverrideKeys = Collections.synchronizedList(new ArrayList<Integer>());
    134 
    135   private float mLastXPosition;
    136 
    137   public UiFacade(FacadeManager manager) {
    138     super(manager);
    139     mService = manager.getService();
    140     mTaskQueue = ((BaseApplication) mService.getApplication()).getTaskExecutor();
    141     mContextMenuItems = new CopyOnWriteArrayList<UiMenuItem>();
    142     mOptionsMenuItems = new CopyOnWriteArrayList<UiMenuItem>();
    143     mEventFacade = manager.getReceiver(EventFacade.class);
    144     mMenuUpdated = new AtomicBoolean(false);
    145   }
    146 
    147   /**
    148    * For inputType, see <a
    149    * href="http://developer.android.com/reference/android/R.styleable.html#TextView_inputType"
    150    * >InputTypes</a>. Some useful ones are text, number, and textUri. Multiple flags can be
    151    * supplied, seperated by "|", ie: "textUri|textAutoComplete"
    152    */
    153   @Rpc(description = "Create a text input dialog.")
    154   public void dialogCreateInput(
    155       @RpcParameter(name = "title", description = "title of the input box") @RpcDefault("Value") final String title,
    156       @RpcParameter(name = "message", description = "message to display above the input box") @RpcDefault("Please enter value:") final String message,
    157       @RpcParameter(name = "defaultText", description = "text to insert into the input box") @RpcOptional final String text,
    158       @RpcParameter(name = "inputType", description = "type of input data, ie number or text") @RpcOptional final String inputType)
    159       throws InterruptedException {
    160     dialogDismiss();
    161     mDialogTask = new AlertDialogTask(title, message);
    162     ((AlertDialogTask) mDialogTask).setTextInput(text);
    163     if (inputType != null) {
    164       ((AlertDialogTask) mDialogTask).setEditInputType(inputType);
    165     }
    166   }
    167 
    168   @Rpc(description = "Create a password input dialog.")
    169   public void dialogCreatePassword(
    170       @RpcParameter(name = "title", description = "title of the input box") @RpcDefault("Password") final String title,
    171       @RpcParameter(name = "message", description = "message to display above the input box") @RpcDefault("Please enter password:") final String message) {
    172     dialogDismiss();
    173     mDialogTask = new AlertDialogTask(title, message);
    174     ((AlertDialogTask) mDialogTask).setPasswordInput();
    175   }
    176 
    177   /**
    178    * The result is the user's input, or None (null) if cancel was hit. <br>
    179    * Example (python)
    180    *
    181    * <pre>
    182    * import android
    183    * droid=android.Android()
    184    *
    185    * print droid.dialogGetInput("Title","Message","Default").result
    186    * </pre>
    187    *
    188    */
    189   @SuppressWarnings("unchecked")
    190   @Rpc(description = "Queries the user for a text input.")
    191   public String dialogGetInput(
    192       @RpcParameter(name = "title", description = "title of the input box") @RpcDefault("Value") final String title,
    193       @RpcParameter(name = "message", description = "message to display above the input box") @RpcDefault("Please enter value:") final String message,
    194       @RpcParameter(name = "defaultText", description = "text to insert into the input box") @RpcOptional final String text)
    195       throws InterruptedException {
    196     dialogCreateInput(title, message, text, "text");
    197     dialogSetNegativeButtonText("Cancel");
    198     dialogSetPositiveButtonText("Ok");
    199     dialogShow();
    200     Map<String, Object> response = (Map<String, Object>) dialogGetResponse();
    201     if ("positive".equals(response.get("which"))) {
    202       return (String) response.get("value");
    203     } else {
    204       return null;
    205     }
    206   }
    207 
    208   @SuppressWarnings("unchecked")
    209   @Rpc(description = "Queries the user for a password.")
    210   public String dialogGetPassword(
    211       @RpcParameter(name = "title", description = "title of the password box") @RpcDefault("Password") final String title,
    212       @RpcParameter(name = "message", description = "message to display above the input box") @RpcDefault("Please enter password:") final String message)
    213       throws InterruptedException {
    214     dialogCreatePassword(title, message);
    215     dialogSetNegativeButtonText("Cancel");
    216     dialogSetPositiveButtonText("Ok");
    217     dialogShow();
    218     Map<String, Object> response = (Map<String, Object>) dialogGetResponse();
    219     if ("positive".equals(response.get("which"))) {
    220       return (String) response.get("value");
    221     } else {
    222       return null;
    223     }
    224   }
    225 
    226   @Rpc(description = "Create a spinner progress dialog.")
    227   public void dialogCreateSpinnerProgress(@RpcParameter(name = "title") @RpcOptional String title,
    228       @RpcParameter(name = "message") @RpcOptional String message,
    229       @RpcParameter(name = "maximum progress") @RpcDefault("100") Integer max) {
    230     dialogDismiss(); // Dismiss any existing dialog.
    231     mDialogTask = new ProgressDialogTask(ProgressDialog.STYLE_SPINNER, max, title, message, true);
    232   }
    233 
    234   @Rpc(description = "Create a horizontal progress dialog.")
    235   public void dialogCreateHorizontalProgress(
    236       @RpcParameter(name = "title") @RpcOptional String title,
    237       @RpcParameter(name = "message") @RpcOptional String message,
    238       @RpcParameter(name = "maximum progress") @RpcDefault("100") Integer max) {
    239     dialogDismiss(); // Dismiss any existing dialog.
    240     mDialogTask =
    241         new ProgressDialogTask(ProgressDialog.STYLE_HORIZONTAL, max, title, message, true);
    242   }
    243 
    244   /**
    245    * <b>Example (python)</b>
    246    *
    247    * <pre>
    248    *   import android
    249    *   droid=android.Android()
    250    *   droid.dialogCreateAlert("I like swords.","Do you like swords?")
    251    *   droid.dialogSetPositiveButtonText("Yes")
    252    *   droid.dialogSetNegativeButtonText("No")
    253    *   droid.dialogShow()
    254    *   response=droid.dialogGetResponse().result
    255    *   droid.dialogDismiss()
    256    *   if response.has_key("which"):
    257    *     result=response["which"]
    258    *     if result=="positive":
    259    *       print "Yay! I like swords too!"
    260    *     elif result=="negative":
    261    *       print "Oh. How sad."
    262    *   elif response.has_key("canceled"): # Yes, I know it's mispelled.
    263    *     print "You can't even make up your mind?"
    264    *   else:
    265    *     print "Unknown response=",response
    266    *
    267    *   print "Done"
    268    * </pre>
    269    */
    270   @Rpc(description = "Create alert dialog.")
    271   public void dialogCreateAlert(@RpcParameter(name = "title") @RpcOptional String title,
    272       @RpcParameter(name = "message") @RpcOptional String message) {
    273     dialogDismiss(); // Dismiss any existing dialog.
    274     mDialogTask = new AlertDialogTask(title, message);
    275   }
    276 
    277   /**
    278    * Will produce "dialog" events on change, containing:
    279    * <ul>
    280    * <li>"progress" - Position chosen, between 0 and max
    281    * <li>"which" = "seekbar"
    282    * <li>"fromuser" = true/false change is from user input
    283    * </ul>
    284    * Response will contain a "progress" element.
    285    */
    286   @Rpc(description = "Create seek bar dialog.")
    287   public void dialogCreateSeekBar(
    288       @RpcParameter(name = "starting value") @RpcDefault("50") Integer progress,
    289       @RpcParameter(name = "maximum value") @RpcDefault("100") Integer max,
    290       @RpcParameter(name = "title") String title, @RpcParameter(name = "message") String message) {
    291     dialogDismiss(); // Dismiss any existing dialog.
    292     mDialogTask = new SeekBarDialogTask(progress, max, title, message);
    293   }
    294 
    295   @Rpc(description = "Create time picker dialog.")
    296   public void dialogCreateTimePicker(
    297       @RpcParameter(name = "hour") @RpcDefault("0") Integer hour,
    298       @RpcParameter(name = "minute") @RpcDefault("0") Integer minute,
    299       @RpcParameter(name = "is24hour", description = "Use 24 hour clock") @RpcDefault("false") Boolean is24hour) {
    300     dialogDismiss(); // Dismiss any existing dialog.
    301     mDialogTask = new TimePickerDialogTask(hour, minute, is24hour);
    302   }
    303 
    304   @Rpc(description = "Create date picker dialog.")
    305   public void dialogCreateDatePicker(@RpcParameter(name = "year") @RpcDefault("1970") Integer year,
    306       @RpcParameter(name = "month") @RpcDefault("1") Integer month,
    307       @RpcParameter(name = "day") @RpcDefault("1") Integer day) {
    308     dialogDismiss(); // Dismiss any existing dialog.
    309     mDialogTask = new DatePickerDialogTask(year, month, day);
    310   }
    311 
    312   @Rpc(description = "Dismiss dialog.")
    313   public void dialogDismiss() {
    314     if (mDialogTask != null) {
    315       mDialogTask.dismissDialog();
    316       mDialogTask = null;
    317     }
    318   }
    319 
    320   @Rpc(description = "Show dialog.")
    321   public void dialogShow() throws InterruptedException {
    322     if (mDialogTask != null && mDialogTask.getDialog() == null) {
    323       mDialogTask.setEventFacade(mEventFacade);
    324       mTaskQueue.execute(mDialogTask);
    325       mDialogTask.getShowLatch().await();
    326     } else {
    327       throw new RuntimeException("No dialog to show.");
    328     }
    329   }
    330 
    331   @Rpc(description = "Set progress dialog current value.")
    332   public void dialogSetCurrentProgress(@RpcParameter(name = "current") Integer current) {
    333     if (mDialogTask != null && mDialogTask instanceof ProgressDialogTask) {
    334       ((ProgressDialog) mDialogTask.getDialog()).setProgress(current);
    335     } else {
    336       throw new RuntimeException("No valid dialog to assign value to.");
    337     }
    338   }
    339 
    340   @Rpc(description = "Set progress dialog maximum value.")
    341   public void dialogSetMaxProgress(@RpcParameter(name = "max") Integer max) {
    342     if (mDialogTask != null && mDialogTask instanceof ProgressDialogTask) {
    343       ((ProgressDialog) mDialogTask.getDialog()).setMax(max);
    344     } else {
    345       throw new RuntimeException("No valid dialog to set maximum value of.");
    346     }
    347   }
    348 
    349   @Rpc(description = "Set alert dialog positive button text.")
    350   public void dialogSetPositiveButtonText(@RpcParameter(name = "text") String text) {
    351     if (mDialogTask != null && mDialogTask instanceof AlertDialogTask) {
    352       ((AlertDialogTask) mDialogTask).setPositiveButtonText(text);
    353     } else if (mDialogTask != null && mDialogTask instanceof SeekBarDialogTask) {
    354       ((SeekBarDialogTask) mDialogTask).setPositiveButtonText(text);
    355     } else {
    356       throw new AndroidRuntimeException("No dialog to add button to.");
    357     }
    358   }
    359 
    360   @Rpc(description = "Set alert dialog button text.")
    361   public void dialogSetNegativeButtonText(@RpcParameter(name = "text") String text) {
    362     if (mDialogTask != null && mDialogTask instanceof AlertDialogTask) {
    363       ((AlertDialogTask) mDialogTask).setNegativeButtonText(text);
    364     } else if (mDialogTask != null && mDialogTask instanceof SeekBarDialogTask) {
    365       ((SeekBarDialogTask) mDialogTask).setNegativeButtonText(text);
    366     } else {
    367       throw new AndroidRuntimeException("No dialog to add button to.");
    368     }
    369   }
    370 
    371   @Rpc(description = "Set alert dialog button text.")
    372   public void dialogSetNeutralButtonText(@RpcParameter(name = "text") String text) {
    373     if (mDialogTask != null && mDialogTask instanceof AlertDialogTask) {
    374       ((AlertDialogTask) mDialogTask).setNeutralButtonText(text);
    375     } else {
    376       throw new AndroidRuntimeException("No dialog to add button to.");
    377     }
    378   }
    379 
    380   // TODO(damonkohler): Make RPC layer translate between JSONArray and List<Object>.
    381   /**
    382    * This effectively creates list of options. Clicking on an item will immediately return an "item"
    383    * element, which is the index of the selected item.
    384    */
    385   @Rpc(description = "Set alert dialog list items.")
    386   public void dialogSetItems(@RpcParameter(name = "items") JSONArray items) {
    387     if (mDialogTask != null && mDialogTask instanceof AlertDialogTask) {
    388       ((AlertDialogTask) mDialogTask).setItems(items);
    389     } else {
    390       throw new AndroidRuntimeException("No dialog to add list to.");
    391     }
    392   }
    393 
    394   /**
    395    * This creates a list of radio buttons. You can select one item out of the list. A response will
    396    * not be returned until the dialog is closed, either with the Cancel key or a button
    397    * (positive/negative/neutral). Use {@link #dialogGetSelectedItems()} to find out what was
    398    * selected.
    399    */
    400   @Rpc(description = "Set dialog single choice items and selected item.")
    401   public void dialogSetSingleChoiceItems(
    402       @RpcParameter(name = "items") JSONArray items,
    403       @RpcParameter(name = "selected", description = "selected item index") @RpcDefault("0") Integer selected) {
    404     if (mDialogTask != null && mDialogTask instanceof AlertDialogTask) {
    405       ((AlertDialogTask) mDialogTask).setSingleChoiceItems(items, selected);
    406     } else {
    407       throw new AndroidRuntimeException("No dialog to add list to.");
    408     }
    409   }
    410 
    411   /**
    412    * This creates a list of check boxes. You can select multiple items out of the list. A response
    413    * will not be returned until the dialog is closed, either with the Cancel key or a button
    414    * (positive/negative/neutral). Use {@link #dialogGetSelectedItems()} to find out what was
    415    * selected.
    416    */
    417 
    418   @Rpc(description = "Set dialog multiple choice items and selection.")
    419   public void dialogSetMultiChoiceItems(
    420       @RpcParameter(name = "items") JSONArray items,
    421       @RpcParameter(name = "selected", description = "list of selected items") @RpcOptional JSONArray selected)
    422       throws JSONException {
    423     if (mDialogTask != null && mDialogTask instanceof AlertDialogTask) {
    424       ((AlertDialogTask) mDialogTask).setMultiChoiceItems(items, selected);
    425     } else {
    426       throw new AndroidRuntimeException("No dialog to add list to.");
    427     }
    428   }
    429 
    430   @Rpc(description = "Returns dialog response.")
    431   public Object dialogGetResponse() {
    432     try {
    433       return mDialogTask.getResult();
    434     } catch (Exception e) {
    435       throw new AndroidRuntimeException(e);
    436     }
    437   }
    438 
    439   @Rpc(description = "This method provides list of items user selected.", returns = "Selected items")
    440   public Set<Integer> dialogGetSelectedItems() {
    441     if (mDialogTask != null && mDialogTask instanceof AlertDialogTask) {
    442       return ((AlertDialogTask) mDialogTask).getSelectedItems();
    443     } else {
    444       throw new AndroidRuntimeException("No dialog to add list to.");
    445     }
    446   }
    447 
    448   @Rpc(description = "Adds a new item to context menu.")
    449   public void addContextMenuItem(
    450       @RpcParameter(name = "label", description = "label for this menu item") String label,
    451       @RpcParameter(name = "event", description = "event that will be generated on menu item click") String event,
    452       @RpcParameter(name = "eventData") @RpcOptional Object data) {
    453     mContextMenuItems.add(new UiMenuItem(label, event, data, null));
    454   }
    455 
    456   /**
    457    * <b>Example (python)</b>
    458    *
    459    * <pre>
    460    * import android
    461    * droid=android.Android()
    462    *
    463    * droid.addOptionsMenuItem("Silly","silly",None,"star_on")
    464    * droid.addOptionsMenuItem("Sensible","sensible","I bet.","star_off")
    465    * droid.addOptionsMenuItem("Off","off",None,"ic_menu_revert")
    466    *
    467    * print "Hit menu to see extra options."
    468    * print "Will timeout in 10 seconds if you hit nothing."
    469    *
    470    * while True: # Wait for events from the menu.
    471    *   response=droid.eventWait(10000).result
    472    *   if response==None:
    473    *     break
    474    *   print response
    475    *   if response["name"]=="off":
    476    *     break
    477    * print "And done."
    478    *
    479    * </pre>
    480    */
    481   @Rpc(description = "Adds a new item to options menu.")
    482   public void addOptionsMenuItem(
    483       @RpcParameter(name = "label", description = "label for this menu item") String label,
    484       @RpcParameter(name = "event", description = "event that will be generated on menu item click") String event,
    485       @RpcParameter(name = "eventData") @RpcOptional Object data,
    486       @RpcParameter(name = "iconName", description = "Android system menu icon, see http://developer.android.com/reference/android/R.drawable.html") @RpcOptional String iconName) {
    487     mOptionsMenuItems.add(new UiMenuItem(label, event, data, iconName));
    488     mMenuUpdated.set(true);
    489   }
    490 
    491   @Rpc(description = "Removes all items previously added to context menu.")
    492   public void clearContextMenu() {
    493     mContextMenuItems.clear();
    494   }
    495 
    496   @Rpc(description = "Removes all items previously added to options menu.")
    497   public void clearOptionsMenu() {
    498     mOptionsMenuItems.clear();
    499     mMenuUpdated.set(true);
    500   }
    501 
    502   public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
    503     for (UiMenuItem item : mContextMenuItems) {
    504       MenuItem menuItem = menu.add(item.mmTitle);
    505       menuItem.setOnMenuItemClickListener(item.mmListener);
    506     }
    507   }
    508 
    509   public boolean onPrepareOptionsMenu(Menu menu) {
    510     if (mMenuUpdated.getAndSet(false)) {
    511       menu.removeGroup(MENU_GROUP_ID);
    512       for (UiMenuItem item : mOptionsMenuItems) {
    513         MenuItem menuItem = menu.add(MENU_GROUP_ID, Menu.NONE, Menu.NONE, item.mmTitle);
    514         if (item.mmIcon != null) {
    515           menuItem.setIcon(mService.getResources()
    516               .getIdentifier(item.mmIcon, "drawable", "android"));
    517         }
    518         menuItem.setOnMenuItemClickListener(item.mmListener);
    519       }
    520       return true;
    521     }
    522     return true;
    523   }
    524 
    525   /**
    526    * See <a href=http://code.google.com/p/android-scripting/wiki/FullScreenUI>wiki page</a> for more
    527    * detail.
    528    */
    529   @Rpc(description = "Show Full Screen.")
    530   public List<String> fullShow(
    531       @RpcParameter(name = "layout", description = "String containing View layout") String layout,
    532       @RpcParameter(name = "title", description = "Activity Title") @RpcOptional String title)
    533       throws InterruptedException {
    534     if (mFullScreenTask != null) {
    535       // fullDismiss();
    536       mFullScreenTask.setLayout(layout);
    537       if (title != null) {
    538         mFullScreenTask.setTitle(title);
    539       }
    540     } else {
    541       mFullScreenTask = new FullScreenTask(layout, title);
    542       mFullScreenTask.setEventFacade(mEventFacade);
    543       mFullScreenTask.setUiFacade(this);
    544       mFullScreenTask.setOverrideKeys(mOverrideKeys);
    545       mTaskQueue.execute(mFullScreenTask);
    546       mFullScreenTask.getShowLatch().await();
    547     }
    548     return mFullScreenTask.mInflater.getErrors();
    549   }
    550 
    551   @Rpc(description = "Dismiss Full Screen.")
    552   public void fullDismiss() {
    553     if (mFullScreenTask != null) {
    554       mFullScreenTask.finish();
    555       mFullScreenTask = null;
    556     }
    557   }
    558 
    559   class MouseMotionListener implements View.OnGenericMotionListener {
    560 
    561       @Override
    562       public boolean onGenericMotion(View v, MotionEvent event) {
    563           Log.d("Generic motion triggered.");
    564           if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
    565               mLastXPosition = event.getAxisValue(MotionEvent.AXIS_X);
    566               Log.d("New mouse x coord: " + mLastXPosition);
    567 //              Bundle msg = new Bundle();
    568 //              msg.putFloat("value", mLastXPosition);
    569 //              mEventFacade.postEvent("MouseXPositionUpdate", msg);
    570               return true;
    571           }
    572           return false;
    573       }
    574   }
    575 
    576   @Rpc(description = "Get Fullscreen Properties")
    577   public Map<String, Map<String, String>> fullQuery() {
    578     if (mFullScreenTask == null) {
    579       throw new RuntimeException("No screen displayed.");
    580     }
    581     return mFullScreenTask.getViewAsMap();
    582   }
    583 
    584   @Rpc(description = "Get fullscreen properties for a specific widget")
    585   public Map<String, String> fullQueryDetail(
    586       @RpcParameter(name = "id", description = "id of layout widget") String id) {
    587     if (mFullScreenTask == null) {
    588       throw new RuntimeException("No screen displayed.");
    589     }
    590     return mFullScreenTask.getViewDetail(id);
    591   }
    592 
    593   @Rpc(description = "Set fullscreen widget property")
    594   public String fullSetProperty(
    595       @RpcParameter(name = "id", description = "id of layout widget") String id,
    596       @RpcParameter(name = "property", description = "name of property to set") String property,
    597       @RpcParameter(name = "value", description = "value to set property to") String value) {
    598     if (mFullScreenTask == null) {
    599       throw new RuntimeException("No screen displayed.");
    600     }
    601     return mFullScreenTask.setViewProperty(id, property, value);
    602   }
    603 
    604   @Rpc(description = "Attach a list to a fullscreen widget")
    605   public String fullSetList(
    606       @RpcParameter(name = "id", description = "id of layout widget") String id,
    607       @RpcParameter(name = "list", description = "List to set") JSONArray items) {
    608     if (mFullScreenTask == null) {
    609       throw new RuntimeException("No screen displayed.");
    610     }
    611     return mFullScreenTask.setList(id, items);
    612   }
    613 
    614   @Rpc(description = "Set the Full Screen Activity Title")
    615   public void fullSetTitle(
    616       @RpcParameter(name = "title", description = "Activity Title") String title) {
    617     if (mFullScreenTask == null) {
    618       throw new RuntimeException("No screen displayed.");
    619     }
    620     mFullScreenTask.setTitle(title);
    621   }
    622 
    623   /**
    624    * This will override the default behaviour of keys while in the fullscreen mode. ie:
    625    *
    626    * <pre>
    627    *   droid.fullKeyOverride([24,25],True)
    628    * </pre>
    629    *
    630    * This will override the default behaviour of the volume keys (codes 24 and 25) so that they do
    631    * not actually adjust the volume. <br>
    632    * Returns a list of currently overridden keycodes.
    633    */
    634   @Rpc(description = "Override default key actions")
    635   public JSONArray fullKeyOverride(
    636       @RpcParameter(name = "keycodes", description = "List of keycodes to override") JSONArray keycodes,
    637       @RpcParameter(name = "enable", description = "Turn overriding or off") @RpcDefault(value = "true") Boolean enable)
    638       throws JSONException {
    639     for (int i = 0; i < keycodes.length(); i++) {
    640       int value = (int) keycodes.getLong(i);
    641       if (value > 0) {
    642         if (enable) {
    643           if (!mOverrideKeys.contains(value)) {
    644             mOverrideKeys.add(value);
    645           }
    646         } else {
    647           int index = mOverrideKeys.indexOf(value);
    648           if (index >= 0) {
    649             mOverrideKeys.remove(index);
    650           }
    651         }
    652       }
    653     }
    654     if (mFullScreenTask != null) {
    655       mFullScreenTask.setOverrideKeys(mOverrideKeys);
    656     }
    657     return new JSONArray(mOverrideKeys);
    658   }
    659 
    660   @Rpc(description = "Start tracking mouse cursor x coordinate.")
    661   public void startTrackingMouseXCoord() throws InterruptedException {
    662     View.OnGenericMotionListener l = new MouseMotionListener();
    663     fullShow(blankLayout, "Blank");
    664     mFullScreenTask.mView.setOnGenericMotionListener(l);
    665   }
    666 
    667   @Rpc(description = "Stop tracking mouse cursor x coordinate.")
    668   public void stopTrackingMouseXCoord() throws InterruptedException {
    669     fullDismiss();
    670   }
    671 
    672   @Rpc(description = "Return the latest X position of mouse cursor.")
    673   public float getLatestMouseXCoord() {
    674       return mLastXPosition;
    675   }
    676 
    677 @Override
    678   public void shutdown() {
    679     fullDismiss();
    680   }
    681 
    682   private class UiMenuItem {
    683 
    684     private final String mmTitle;
    685     private final String mmEvent;
    686     private final Object mmEventData;
    687     private final String mmIcon;
    688     private final MenuItem.OnMenuItemClickListener mmListener;
    689 
    690     public UiMenuItem(String title, String event, Object data, String icon) {
    691       mmTitle = title;
    692       mmEvent = event;
    693       mmEventData = data;
    694       mmIcon = icon;
    695       mmListener = new MenuItem.OnMenuItemClickListener() {
    696         @Override
    697         public boolean onMenuItemClick(MenuItem item) {
    698           // TODO(damonkohler): Does mmEventData need to be cloned somehow?
    699           mEventFacade.postEvent(mmEvent, mmEventData);
    700           return true;
    701         }
    702       };
    703     }
    704   }
    705 }
    706