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