• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
UiFacade(FacadeManager manager)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.")
dialogCreateInput( @pcParametername = "title", description = "title of the input box") @pcDefault"Value") final String title, @RpcParameter(name = "message", description = "message to display above the input box") @RpcDefault("Please enter value:") final String message, @RpcParameter(name = "defaultText", description = "text to insert into the input box") @RpcOptional final String text, @RpcParameter(name = "inputType", description = "type of input data, ie number or text") @RpcOptional final String inputType)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.")
dialogCreatePassword( @pcParametername = "title", description = "title of the input box") @pcDefault"Password") final String title, @RpcParameter(name = "message", description = "message to display above the input box") @RpcDefault("Please enter password:") final String message)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.")
dialogGetInput( @pcParametername = "title", description = "title of the input box") @pcDefault"Value") final String title, @RpcParameter(name = "message", description = "message to display above the input box") @RpcDefault("Please enter value:") final String message, @RpcParameter(name = "defaultText", description = "text to insert into the input box") @RpcOptional final String text)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.")
dialogGetPassword( @pcParametername = "title", description = "title of the password box") @pcDefault"Password") final String title, @RpcParameter(name = "message", description = "message to display above the input box") @RpcDefault("Please enter password:") final String message)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.")
dialogCreateSpinnerProgress(@pcParametername = "title") @pcOptional String title, @RpcParameter(name = "message") @RpcOptional String message, @RpcParameter(name = "maximum progress") @RpcDefault("100") Integer max)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.")
dialogCreateHorizontalProgress( @pcParametername = "title") @pcOptional String title, @RpcParameter(name = "message") @RpcOptional String message, @RpcParameter(name = "maximum progress") @RpcDefault("100") Integer max)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.")
dialogCreateAlert(@pcParametername = "title") @pcOptional String title, @RpcParameter(name = "message") @RpcOptional String message)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.")
dialogCreateSeekBar( @pcParametername = "starting value") @pcDefault"50") Integer progress, @RpcParameter(name = "maximum value") @RpcDefault("100") Integer max, @RpcParameter(name = "title") String title, @RpcParameter(name = "message") String message)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.")
dialogCreateTimePicker( @pcParametername = "hour") @pcDefault"0") Integer hour, @RpcParameter(name = "minute") @RpcDefault("0") Integer minute, @RpcParameter(name = "is24hour", description = "Use 24 hour clock") @RpcDefault("false") Boolean is24hour)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.")
dialogCreateDatePicker(@pcParametername = "year") @pcDefault"1970") Integer year, @RpcParameter(name = "month") @RpcDefault("1") Integer month, @RpcParameter(name = "day") @RpcDefault("1") Integer day)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.")
dialogDismiss()315   public void dialogDismiss() {
316     if (mDialogTask != null) {
317       mDialogTask.dismissDialog();
318       mDialogTask = null;
319     }
320   }
321 
322   @Rpc(description = "Show dialog.")
dialogShow()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.")
dialogSetCurrentProgress(@pcParametername = "current") Integer current)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.")
dialogSetMaxProgress(@pcParametername = "max") Integer max)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.")
dialogSetPositiveButtonText(@pcParametername = "text") String 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.")
dialogSetNegativeButtonText(@pcParametername = "text") String 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.")
dialogSetNeutralButtonText(@pcParametername = "text") String 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.")
dialogSetItems(@pcParametername = "items") JSONArray 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.")
dialogSetSingleChoiceItems( @pcParametername = "items") JSONArray items, @RpcParameter(name = "selected", description = "selected item index") @RpcDefault("0") Integer selected)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.")
dialogSetMultiChoiceItems( @pcParametername = "items") JSONArray items, @RpcParameter(name = "selected", description = "list of selected items") @RpcOptional JSONArray selected)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.")
dialogGetResponse()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")
dialogGetSelectedItems()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.")
addContextMenuItem( @pcParametername = "label", description = "label for this menu item") String label, @RpcParameter(name = "event", description = "event that will be generated on menu item click") String event, @RpcParameter(name = "eventData") @RpcOptional Object data)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.")
addOptionsMenuItem( @pcParametername = "label", description = "label for this menu item") String label, @RpcParameter(name = "event", description = "event that will be generated on menu item click") String event, @RpcParameter(name = "eventData") @RpcOptional Object data, @RpcParameter(name = "iconName", description = "Android system menu icon, see http://developer.android.com/reference/android/R.drawable.html") @RpcOptional String iconName)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.")
clearContextMenu()494   public void clearContextMenu() {
495     mContextMenuItems.clear();
496   }
497 
498   @Rpc(description = "Removes all items previously added to options menu.")
clearOptionsMenu()499   public void clearOptionsMenu() {
500     mOptionsMenuItems.clear();
501     mMenuUpdated.set(true);
502   }
503 
onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)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 
onPrepareOptionsMenu(Menu menu)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.")
fullShow( @pcParametername = "layout", description = "String containing View layout") String layout, @RpcParameter(name = "title", description = "Activity Title") @RpcOptional String title)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.")
fullDismiss()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
onGenericMotion(View v, MotionEvent event)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")
fullQuery()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")
fullQueryDetail( @pcParametername = "id", description = "id of layout widget") String id)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")
fullSetProperty( @pcParametername = "id", description = "id of layout widget") String id, @RpcParameter(name = "property", description = "name of property to set") String property, @RpcParameter(name = "value", description = "value to set property to") String value)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")
fullSetList( @pcParametername = "id", description = "id of layout widget") String id, @RpcParameter(name = "list", description = "List to set") JSONArray items)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")
fullSetTitle( @pcParametername = "title", description = "Activity Title") String 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")
fullKeyOverride( @pcParametername = "keycodes", description = "List of keycodes to override") JSONArray keycodes, @RpcParameter(name = "enable", description = "Turn overriding or off") @RpcDefault(value = "true") Boolean enable)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.")
startTrackingMouseXCoord()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.")
stopTrackingMouseXCoord()670   public void stopTrackingMouseXCoord() throws InterruptedException {
671     fullDismiss();
672   }
673 
674   @Rpc(description = "Return the latest X position of mouse cursor.")
getLatestMouseXCoord()675   public float getLatestMouseXCoord() {
676       return mLastXPosition;
677   }
678 
679 @Override
shutdown()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 
UiMenuItem(String title, String event, Object data, String icon)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