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
17package com.googlecode.android_scripting.facade.ui;
18
19import android.app.ProgressDialog;
20import android.app.Service;
21import android.util.AndroidRuntimeException;
22import android.view.ContextMenu;
23import android.view.ContextMenu.ContextMenuInfo;
24import android.view.Menu;
25import android.view.MenuItem;
26import android.view.MotionEvent;
27import android.view.View;
28
29import com.googlecode.android_scripting.BaseApplication;
30import com.googlecode.android_scripting.FileUtils;
31import com.googlecode.android_scripting.FutureActivityTaskExecutor;
32import com.googlecode.android_scripting.Log;
33import com.googlecode.android_scripting.facade.EventFacade;
34import com.googlecode.android_scripting.facade.FacadeManager;
35import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
36import com.googlecode.android_scripting.rpc.Rpc;
37import com.googlecode.android_scripting.rpc.RpcDefault;
38import com.googlecode.android_scripting.rpc.RpcOptional;
39import com.googlecode.android_scripting.rpc.RpcParameter;
40
41import java.io.IOException;
42import java.util.ArrayList;
43import java.util.Collections;
44import java.util.List;
45import java.util.Map;
46import java.util.Set;
47import java.util.concurrent.CopyOnWriteArrayList;
48import java.util.concurrent.atomic.AtomicBoolean;
49
50import org.json.JSONArray;
51import 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 */
116public 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