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