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