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