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