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