DreamService.java revision 4c1e3183baf39ab69c0289c1511877a8bb0b0f75
1/** 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package android.service.dreams; 17 18import java.io.FileDescriptor; 19import java.io.PrintWriter; 20 21import android.annotation.SdkConstant; 22import android.annotation.SdkConstant.SdkConstantType; 23import android.app.Service; 24import android.content.Intent; 25import android.graphics.PixelFormat; 26import android.graphics.drawable.ColorDrawable; 27import android.os.Handler; 28import android.os.IBinder; 29import android.os.ServiceManager; 30import android.util.Slog; 31import android.view.ActionMode; 32import android.view.KeyEvent; 33import android.view.Menu; 34import android.view.MenuItem; 35import android.view.MotionEvent; 36import android.view.View; 37import android.view.ViewGroup; 38import android.view.Window; 39import android.view.WindowManager; 40import android.view.WindowManager.LayoutParams; 41import android.view.accessibility.AccessibilityEvent; 42 43import com.android.internal.policy.PolicyManager; 44 45/** 46 * Extend this class to implement a custom Dream (displayed to the user as a "Sleep Mode"). 47 * 48 * <p>Dreams are interactive screensavers launched when a charging device is idle, or docked in a 49 * desk dock. Dreams provide another modality for apps to express themselves, tailored for 50 * an exhibition/lean-back experience.</p> 51 * 52 * <p>The Dream lifecycle is as follows:</p> 53 * <ul> 54 * <li>onAttachedToWindow</li> 55 * <li>onDreamingStarted</li> 56 * <li>onDreamingStopped</li> 57 * <li>onDetachedFromWindow</li> 58 * </ul> 59 * 60 * <p>In addition, onCreate and onDestroy (from the Service interface) will also be called, but 61 * initialization and teardown should be done by overriding the hooks above.</p> 62 * 63 * <p>To be available to the system, Dreams should be declared in the manifest as follows:</p> 64 * <pre> 65 * <service 66 * android:name=".MyDream" 67 * android:exported="true" 68 * android:icon="@drawable/my_icon" 69 * android:label="@string/my_dream_label" > 70 * 71 * <intent-filter> 72 * <action android:name="android.service.dreams.DreamService" /> 73 * <category android:name="android.intent.category.DEFAULT" /> 74 * </intent-filter> 75 * 76 * <!-- Point to additional information for this dream (optional) --> 77 * <meta-data 78 * android:name="android.service.dream" 79 * android:resource="@xml/my_dream" /> 80 * </service> 81 * </pre> 82 * <p>If specified, additional information for the dream is defined using the 83 * <code><{@link android.R.styleable#Dream dream}></code> element. For example:</p> 84 * <pre> 85 * (in res/xml/my_dream.xml) 86 * 87 * <dream xmlns:android="http://schemas.android.com/apk/res/android" 88 * android:settingsActivity="com.example.app/.MyDreamSettingsActivity" /> 89 * </pre> 90 */ 91public class DreamService extends Service implements Window.Callback { 92 private final String TAG = DreamService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]"; 93 94 /** 95 * The name of the dream manager service. 96 * @hide 97 */ 98 public static final String DREAM_SERVICE = "dreams"; 99 100 /** 101 * The {@link Intent} that must be declared as handled by the service. 102 */ 103 @SdkConstant(SdkConstantType.SERVICE_ACTION) 104 public static final String SERVICE_INTERFACE = 105 "android.service.dreams.DreamService"; 106 107 /** 108 * Name under which a Dream publishes information about itself. 109 * This meta-data must reference an XML resource containing 110 * a <code><{@link android.R.styleable#Dream dream}></code> 111 * tag. 112 */ 113 public static final String DREAM_META_DATA = "android.service.dream"; 114 115 private final Handler mHandler = new Handler(); 116 private IBinder mWindowToken; 117 private Window mWindow; 118 private WindowManager mWindowManager; 119 private IDreamManager mSandman; 120 private boolean mInteractive = false; 121 private boolean mLowProfile = true; 122 private boolean mFullscreen = false; 123 private boolean mScreenBright = false; 124 private boolean mFinished; 125 126 private boolean mDebug = false; 127 128 /** 129 * @hide 130 */ 131 public void setDebug(boolean dbg) { 132 mDebug = dbg; 133 } 134 135 // begin Window.Callback methods 136 /** {@inheritDoc} */ 137 @Override 138 public boolean dispatchKeyEvent(KeyEvent event) { 139 // TODO: create more flexible version of mInteractive that allows use of KEYCODE_BACK 140 if (!mInteractive) { 141 if (mDebug) Slog.v(TAG, "Finishing on keyEvent"); 142 safelyFinish(); 143 return true; 144 } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { 145 if (mDebug) Slog.v(TAG, "Finishing on back key"); 146 safelyFinish(); 147 return true; 148 } 149 return mWindow.superDispatchKeyEvent(event); 150 } 151 152 /** {@inheritDoc} */ 153 @Override 154 public boolean dispatchKeyShortcutEvent(KeyEvent event) { 155 if (!mInteractive) { 156 if (mDebug) Slog.v(TAG, "Finishing on keyShortcutEvent"); 157 safelyFinish(); 158 return true; 159 } 160 return mWindow.superDispatchKeyShortcutEvent(event); 161 } 162 163 /** {@inheritDoc} */ 164 @Override 165 public boolean dispatchTouchEvent(MotionEvent event) { 166 // TODO: create more flexible version of mInteractive that allows clicks 167 // but finish()es on any other kind of activity 168 if (!mInteractive) { 169 if (mDebug) Slog.v(TAG, "Finishing on touchEvent"); 170 safelyFinish(); 171 return true; 172 } 173 return mWindow.superDispatchTouchEvent(event); 174 } 175 176 /** {@inheritDoc} */ 177 @Override 178 public boolean dispatchTrackballEvent(MotionEvent event) { 179 if (!mInteractive) { 180 if (mDebug) Slog.v(TAG, "Finishing on trackballEvent"); 181 safelyFinish(); 182 return true; 183 } 184 return mWindow.superDispatchTrackballEvent(event); 185 } 186 187 /** {@inheritDoc} */ 188 @Override 189 public boolean dispatchGenericMotionEvent(MotionEvent event) { 190 if (!mInteractive) { 191 if (mDebug) Slog.v(TAG, "Finishing on genericMotionEvent"); 192 safelyFinish(); 193 return true; 194 } 195 return mWindow.superDispatchGenericMotionEvent(event); 196 } 197 198 /** {@inheritDoc} */ 199 @Override 200 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 201 return false; 202 } 203 204 /** {@inheritDoc} */ 205 @Override 206 public View onCreatePanelView(int featureId) { 207 return null; 208 } 209 210 /** {@inheritDoc} */ 211 @Override 212 public boolean onCreatePanelMenu(int featureId, Menu menu) { 213 return false; 214 } 215 216 /** {@inheritDoc} */ 217 @Override 218 public boolean onPreparePanel(int featureId, View view, Menu menu) { 219 return false; 220 } 221 222 /** {@inheritDoc} */ 223 @Override 224 public boolean onMenuOpened(int featureId, Menu menu) { 225 return false; 226 } 227 228 /** {@inheritDoc} */ 229 @Override 230 public boolean onMenuItemSelected(int featureId, MenuItem item) { 231 return false; 232 } 233 234 /** {@inheritDoc} */ 235 @Override 236 public void onWindowAttributesChanged(LayoutParams attrs) { 237 } 238 239 /** {@inheritDoc} */ 240 @Override 241 public void onContentChanged() { 242 } 243 244 /** {@inheritDoc} */ 245 @Override 246 public void onWindowFocusChanged(boolean hasFocus) { 247 } 248 249 /** {@inheritDoc} */ 250 @Override 251 public void onAttachedToWindow() { 252 } 253 254 /** {@inheritDoc} */ 255 @Override 256 public void onDetachedFromWindow() { 257 } 258 259 /** {@inheritDoc} */ 260 @Override 261 public void onPanelClosed(int featureId, Menu menu) { 262 } 263 264 /** {@inheritDoc} */ 265 @Override 266 public boolean onSearchRequested() { 267 return false; 268 } 269 270 /** {@inheritDoc} */ 271 @Override 272 public ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback callback) { 273 return null; 274 } 275 276 /** {@inheritDoc} */ 277 @Override 278 public void onActionModeStarted(ActionMode mode) { 279 } 280 281 /** {@inheritDoc} */ 282 @Override 283 public void onActionModeFinished(ActionMode mode) { 284 } 285 // end Window.Callback methods 286 287 // begin public api 288 /** 289 * Retrieves the current {@link android.view.WindowManager} for the dream. 290 * Behaves similarly to {@link android.app.Activity#getWindowManager()}. 291 * 292 * @return The current window manager, or null if the dream is not started. 293 */ 294 public WindowManager getWindowManager() { 295 return mWindowManager; 296 } 297 298 /** 299 * Retrieves the current {@link android.view.Window} for the dream. 300 * Behaves similarly to {@link android.app.Activity#getWindow()}. 301 * 302 * @return The current window, or null if the dream is not started. 303 */ 304 public Window getWindow() { 305 return mWindow; 306 } 307 308 /** 309 * Inflates a layout resource and set it to be the content view for this Dream. 310 * Behaves similarly to {@link android.app.Activity#setContentView(int)}. 311 * 312 * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p> 313 * 314 * @param layoutResID Resource ID to be inflated. 315 * 316 * @see #setContentView(android.view.View) 317 * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams) 318 */ 319 public void setContentView(int layoutResID) { 320 getWindow().setContentView(layoutResID); 321 } 322 323 /** 324 * Sets a view to be the content view for this Dream. 325 * Behaves similarly to {@link android.app.Activity#setContentView(android.view.View)}, 326 * including using {@link ViewGroup.LayoutParams#MATCH_PARENT} as the layout height and width of the view. 327 * 328 * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p> 329 * @param view The desired content to display. 330 * 331 * @see #setContentView(int) 332 * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams) 333 */ 334 public void setContentView(View view) { 335 getWindow().setContentView(view); 336 } 337 338 /** 339 * Sets a view to be the content view for this Dream. 340 * Behaves similarly to 341 * {@link android.app.Activity#setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}. 342 * 343 * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p> 344 * 345 * @param view The desired content to display. 346 * @param params Layout parameters for the view. 347 * 348 * @see #setContentView(android.view.View) 349 * @see #setContentView(int) 350 */ 351 public void setContentView(View view, ViewGroup.LayoutParams params) { 352 getWindow().setContentView(view, params); 353 } 354 355 /** 356 * Adds a view to the Dream's window, leaving other content views in place. 357 * 358 * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p> 359 * 360 * @param view The desired content to display. 361 * @param params Layout parameters for the view. 362 */ 363 public void addContentView(View view, ViewGroup.LayoutParams params) { 364 getWindow().addContentView(view, params); 365 } 366 367 /** 368 * Finds a view that was identified by the id attribute from the XML that 369 * was processed in {@link #onCreate}. 370 * 371 * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p> 372 * 373 * @return The view if found or null otherwise. 374 */ 375 public View findViewById(int id) { 376 return getWindow().findViewById(id); 377 } 378 379 /** 380 * Marks this dream as interactive to receive input events. 381 * 382 * <p>Non-interactive dreams (default) will dismiss on the first input event.</p> 383 * 384 * <p>Interactive dreams should call {@link #finish()} to dismiss themselves.</p> 385 * 386 * @param interactive True if this dream will handle input events. 387 */ 388 public void setInteractive(boolean interactive) { 389 mInteractive = interactive; 390 } 391 392 /** 393 * Returns whether or not this dream is interactive. Defaults to false. 394 * 395 * @see #setInteractive(boolean) 396 */ 397 public boolean isInteractive() { 398 return mInteractive; 399 } 400 401 /** 402 * Sets View.SYSTEM_UI_FLAG_LOW_PROFILE on the content view. 403 * 404 * @param lowProfile True to set View.SYSTEM_UI_FLAG_LOW_PROFILE 405 * @hide There is no reason to have this -- dreams can set this flag 406 * on their own content view, and from there can actually do the 407 * correct interactions with it (seeing when it is cleared etc). 408 */ 409 public void setLowProfile(boolean lowProfile) { 410 mLowProfile = lowProfile; 411 int flag = View.SYSTEM_UI_FLAG_LOW_PROFILE; 412 applySystemUiVisibilityFlags(mLowProfile ? flag : 0, flag); 413 } 414 415 /** 416 * Returns whether or not this dream is in low profile mode. Defaults to true. 417 * 418 * @see #setLowProfile(boolean) 419 * @hide 420 */ 421 public boolean isLowProfile() { 422 return getSystemUiVisibilityFlagValue(View.SYSTEM_UI_FLAG_LOW_PROFILE, mLowProfile); 423 } 424 425 /** 426 * Controls {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN} 427 * on the dream's window. 428 * 429 * @param fullscreen If true, the fullscreen flag will be set; else it 430 * will be cleared. 431 */ 432 public void setFullscreen(boolean fullscreen) { 433 mFullscreen = fullscreen; 434 int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN; 435 applyWindowFlags(mFullscreen ? flag : 0, flag); 436 } 437 438 /** 439 * Returns whether or not this dream is in fullscreen mode. Defaults to false. 440 * 441 * @see #setFullscreen(boolean) 442 */ 443 public boolean isFullscreen() { 444 return mFullscreen; 445 } 446 447 /** 448 * Marks this dream as keeping the screen bright while dreaming. 449 * 450 * @param screenBright True to keep the screen bright while dreaming. 451 */ 452 public void setScreenBright(boolean screenBright) { 453 mScreenBright = screenBright; 454 int flag = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; 455 applyWindowFlags(mScreenBright ? flag : 0, flag); 456 } 457 458 /** 459 * Returns whether or not this dream keeps the screen bright while dreaming. Defaults to false, 460 * allowing the screen to dim if necessary. 461 * 462 * @see #setScreenBright(boolean) 463 */ 464 public boolean isScreenBright() { 465 return getWindowFlagValue(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, mScreenBright); 466 } 467 468 /** 469 * Called when this Dream is constructed. 470 */ 471 @Override 472 public void onCreate() { 473 if (mDebug) Slog.v(TAG, "onCreate() on thread " + Thread.currentThread().getId()); 474 super.onCreate(); 475 } 476 477 /** 478 * Called when the dream's window has been created and is visible and animation may now begin. 479 */ 480 public void onDreamingStarted() { 481 if (mDebug) Slog.v(TAG, "onDreamingStarted()"); 482 // hook for subclasses 483 } 484 485 /** 486 * Called when this Dream is stopped, either by external request or by calling finish(), 487 * before the window has been removed. 488 */ 489 public void onDreamingStopped() { 490 if (mDebug) Slog.v(TAG, "onDreamingStopped()"); 491 // hook for subclasses 492 } 493 494 /** {@inheritDoc} */ 495 @Override 496 public final IBinder onBind(Intent intent) { 497 if (mDebug) Slog.v(TAG, "onBind() intent = " + intent); 498 return new DreamServiceWrapper(); 499 } 500 501 /** 502 * Stops the dream, detaches from the window, and wakes up. 503 */ 504 public final void finish() { 505 if (mDebug) Slog.v(TAG, "finish()"); 506 finishInternal(); 507 } 508 509 /** {@inheritDoc} */ 510 @Override 511 public void onDestroy() { 512 if (mDebug) Slog.v(TAG, "onDestroy()"); 513 super.onDestroy(); 514 // hook for subclasses 515 } 516 517 // end public api 518 519 private void loadSandman() { 520 mSandman = IDreamManager.Stub.asInterface(ServiceManager.getService(DREAM_SERVICE)); 521 } 522 523 /** 524 * Called when the Dream is about to be unbound and destroyed. 525 * 526 * Must run on mHandler. 527 */ 528 private final void detach() { 529 if (mWindow == null) { 530 Slog.e(TAG, "detach() called when not attached"); 531 return; 532 } 533 534 try { 535 onDreamingStopped(); 536 } catch (Throwable t) { 537 Slog.w(TAG, "Crashed in onDreamingStopped()", t); 538 // we were going to stop anyway 539 } 540 541 if (mDebug) Slog.v(TAG, "detach(): Removing window from window manager"); 542 try { 543 mWindowManager.removeView(mWindow.getDecorView()); 544 } catch (Throwable t) { 545 Slog.w(TAG, "Crashed removing window view", t); 546 } 547 548 mWindow = null; 549 mWindowToken = null; 550 } 551 552 /** 553 * Called when the Dream is ready to be shown. 554 * 555 * Must run on mHandler. 556 * 557 * @param windowToken A window token that will allow a window to be created in the correct layer. 558 */ 559 private final void attach(IBinder windowToken) { 560 if (mWindowToken != null) { 561 Slog.e(TAG, "attach() called when already attached with token=" + mWindowToken); 562 return; 563 } 564 565 if (mDebug) Slog.v(TAG, "Attached on thread " + Thread.currentThread().getId()); 566 567 if (mSandman == null) { 568 loadSandman(); 569 } 570 mWindowToken = windowToken; 571 mWindow = PolicyManager.makeNewWindow(this); 572 mWindow.setCallback(this); 573 mWindow.requestFeature(Window.FEATURE_NO_TITLE); 574 mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000)); 575 mWindow.setFormat(PixelFormat.OPAQUE); 576 577 if (mDebug) Slog.v(TAG, String.format("Attaching window token: %s to window of type %s", 578 windowToken, WindowManager.LayoutParams.TYPE_DREAM)); 579 580 WindowManager.LayoutParams lp = mWindow.getAttributes(); 581 lp.type = WindowManager.LayoutParams.TYPE_DREAM; 582 lp.token = windowToken; 583 lp.windowAnimations = com.android.internal.R.style.Animation_Dream; 584 lp.flags |= ( WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 585 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR 586 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED 587 | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD 588 | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON 589 | (mFullscreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0) 590 | (mScreenBright ? WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON : 0) 591 ); 592 mWindow.setAttributes(lp); 593 594 if (mDebug) Slog.v(TAG, "Created and attached window: " + mWindow); 595 596 mWindow.setWindowManager(null, windowToken, "dream", true); 597 mWindowManager = mWindow.getWindowManager(); 598 599 if (mDebug) Slog.v(TAG, "Window added on thread " + Thread.currentThread().getId()); 600 try { 601 applySystemUiVisibilityFlags( 602 (mLowProfile ? View.SYSTEM_UI_FLAG_LOW_PROFILE : 0), 603 View.SYSTEM_UI_FLAG_LOW_PROFILE); 604 getWindowManager().addView(mWindow.getDecorView(), mWindow.getAttributes()); 605 } catch (Throwable t) { 606 Slog.w("Crashed adding window view", t); 607 safelyFinish(); 608 return; 609 } 610 611 // start it up 612 try { 613 onDreamingStarted(); 614 } catch (Throwable t) { 615 Slog.w("Crashed in onDreamingStarted()", t); 616 safelyFinish(); 617 } 618 } 619 620 private void safelyFinish() { 621 if (mDebug) Slog.v(TAG, "safelyFinish()"); 622 try { 623 finish(); 624 } catch (Throwable t) { 625 Slog.w(TAG, "Crashed in safelyFinish()", t); 626 finishInternal(); 627 return; 628 } 629 630 if (!mFinished) { 631 Slog.w(TAG, "Bad dream, did not call super.finish()"); 632 finishInternal(); 633 } 634 } 635 636 private void finishInternal() { 637 if (mDebug) Slog.v(TAG, "finishInternal() mFinished = " + mFinished); 638 if (mFinished) return; 639 try { 640 mFinished = true; 641 642 if (mSandman != null) { 643 mSandman.finishSelf(mWindowToken); 644 } else { 645 Slog.w(TAG, "No dream manager found"); 646 } 647 stopSelf(); // if launched via any other means 648 649 } catch (Throwable t) { 650 Slog.w(TAG, "Crashed in finishInternal()", t); 651 } 652 } 653 654 private boolean getWindowFlagValue(int flag, boolean defaultValue) { 655 return mWindow == null ? defaultValue : (mWindow.getAttributes().flags & flag) != 0; 656 } 657 658 private void applyWindowFlags(int flags, int mask) { 659 if (mWindow != null) { 660 WindowManager.LayoutParams lp = mWindow.getAttributes(); 661 lp.flags = applyFlags(lp.flags, flags, mask); 662 mWindow.setAttributes(lp); 663 mWindowManager.updateViewLayout(mWindow.getDecorView(), lp); 664 } 665 } 666 667 private boolean getSystemUiVisibilityFlagValue(int flag, boolean defaultValue) { 668 View v = mWindow == null ? null : mWindow.getDecorView(); 669 return v == null ? defaultValue : (v.getSystemUiVisibility() & flag) != 0; 670 } 671 672 private void applySystemUiVisibilityFlags(int flags, int mask) { 673 View v = mWindow == null ? null : mWindow.getDecorView(); 674 if (v != null) { 675 v.setSystemUiVisibility(applyFlags(v.getSystemUiVisibility(), flags, mask)); 676 } 677 } 678 679 private int applyFlags(int oldFlags, int flags, int mask) { 680 return (oldFlags&~mask) | (flags&mask); 681 } 682 683 @Override 684 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 685 super.dump(fd, pw, args); 686 687 pw.print(TAG + ": "); 688 if (mWindowToken == null) { 689 pw.println("stopped"); 690 } else { 691 pw.println("running (token=" + mWindowToken + ")"); 692 } 693 pw.println(" window: " + mWindow); 694 pw.print(" flags:"); 695 if (isInteractive()) pw.print(" interactive"); 696 if (isLowProfile()) pw.print(" lowprofile"); 697 if (isFullscreen()) pw.print(" fullscreen"); 698 if (isScreenBright()) pw.print(" bright"); 699 pw.println(); 700 } 701 702 private class DreamServiceWrapper extends IDreamService.Stub { 703 public void attach(final IBinder windowToken) { 704 mHandler.post(new Runnable() { 705 @Override 706 public void run() { 707 DreamService.this.attach(windowToken); 708 } 709 }); 710 } 711 public void detach() { 712 mHandler.post(new Runnable() { 713 @Override 714 public void run() { 715 DreamService.this.detach(); 716 } 717 }); 718 } 719 } 720 721} 722