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