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