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 */ 16 17 package com.android.server.dreams; 18 19 import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; 20 import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER; 21 import static android.os.PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS; 22 23 import android.app.ActivityTaskManager; 24 import android.app.BroadcastOptions; 25 import android.app.IAppTask; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.ServiceConnection; 30 import android.os.Binder; 31 import android.os.Bundle; 32 import android.os.Handler; 33 import android.os.IBinder; 34 import android.os.IBinder.DeathRecipient; 35 import android.os.IRemoteCallback; 36 import android.os.PowerManager; 37 import android.os.RemoteException; 38 import android.os.SystemClock; 39 import android.os.Trace; 40 import android.os.UserHandle; 41 import android.service.dreams.DreamService; 42 import android.service.dreams.IDreamService; 43 import android.util.Slog; 44 45 import com.android.internal.logging.MetricsLogger; 46 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 47 48 import java.io.PrintWriter; 49 import java.util.ArrayList; 50 import java.util.Iterator; 51 import java.util.NoSuchElementException; 52 import java.util.Objects; 53 import java.util.UUID; 54 55 /** 56 * Internal controller for starting and stopping the current dream and managing related state. 57 * 58 * Assumes all operations are called from the dream handler thread. 59 */ 60 final class DreamController { 61 private static final String TAG = "DreamController"; 62 63 // How long we wait for a newly bound dream to create the service connection 64 private static final int DREAM_CONNECTION_TIMEOUT = 5 * 1000; 65 66 // Time to allow the dream to perform an exit transition when waking up. 67 private static final int DREAM_FINISH_TIMEOUT = 5 * 1000; 68 69 // Extras used with ACTION_CLOSE_SYSTEM_DIALOGS broadcast 70 private static final String EXTRA_REASON_KEY = "reason"; 71 private static final String EXTRA_REASON_VALUE = "dream"; 72 73 private final Context mContext; 74 private final Handler mHandler; 75 private final Listener mListener; 76 private final ActivityTaskManager mActivityTaskManager; 77 private final PowerManager mPowerManager; 78 79 private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED) 80 .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | FLAG_RECEIVER_FOREGROUND); 81 private final Intent mDreamingStoppedIntent = new Intent(Intent.ACTION_DREAMING_STOPPED) 82 .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | FLAG_RECEIVER_FOREGROUND); 83 private static final String DREAMING_DELIVERY_GROUP_NAMESPACE = UUID.randomUUID().toString(); 84 private static final String DREAMING_DELIVERY_GROUP_KEY = UUID.randomUUID().toString(); 85 private final Bundle mDreamingStartedStoppedOptions = createDreamingStartedStoppedOptions(); 86 87 private final Intent mCloseNotificationShadeIntent; 88 private final Bundle mCloseNotificationShadeOptions; 89 90 /** 91 * If this flag is on, we report user activity to {@link PowerManager} so that the screen 92 * doesn't shut off immediately when a dream quits unexpectedly. The device will instead go to 93 * keyguard and time out back to dreaming shortly. 94 * 95 * This allows the dream a second chance to relaunch in case of an app update or other crash. 96 */ 97 private final boolean mResetScreenTimeoutOnUnexpectedDreamExit; 98 99 private DreamRecord mCurrentDream; 100 101 // Whether a dreaming started intent has been broadcast. 102 private boolean mSentStartBroadcast = false; 103 104 // When a new dream is started and there is an existing dream, the existing dream is allowed to 105 // live a little longer until the new dream is started, for a smoother transition. This dream is 106 // stopped as soon as the new dream is started, and this list is cleared. Usually there should 107 // only be one previous dream while waiting for a new dream to start, but we store a list to 108 // proof the edge case of multiple previous dreams. 109 private final ArrayList<DreamRecord> mPreviousDreams = new ArrayList<>(); 110 DreamController(Context context, Handler handler, Listener listener)111 public DreamController(Context context, Handler handler, Listener listener) { 112 mContext = context; 113 mHandler = handler; 114 mListener = listener; 115 mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class); 116 mPowerManager = mContext.getSystemService(PowerManager.class); 117 mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 118 mCloseNotificationShadeIntent.putExtra(EXTRA_REASON_KEY, EXTRA_REASON_VALUE); 119 mCloseNotificationShadeIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 120 mCloseNotificationShadeOptions = BroadcastOptions.makeBasic() 121 .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) 122 .setDeliveryGroupMatchingKey(Intent.ACTION_CLOSE_SYSTEM_DIALOGS, 123 EXTRA_REASON_VALUE) 124 .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) 125 .toBundle(); 126 mResetScreenTimeoutOnUnexpectedDreamExit = context.getResources().getBoolean( 127 com.android.internal.R.bool.config_resetScreenTimeoutOnUnexpectedDreamExit); 128 } 129 130 /** 131 * Create the {@link BroadcastOptions} bundle that will be used with sending the 132 * {@link Intent#ACTION_DREAMING_STARTED} and {@link Intent#ACTION_DREAMING_STOPPED} 133 * broadcasts. 134 */ createDreamingStartedStoppedOptions()135 private Bundle createDreamingStartedStoppedOptions() { 136 final BroadcastOptions options = BroadcastOptions.makeBasic(); 137 // This allows the broadcasting system to discard any older broadcasts 138 // waiting to be delivered to a process. 139 options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); 140 // Set namespace and key to identify which older broadcasts can be discarded. 141 // We could use any strings here with the following requirements: 142 // - namespace needs to be unlikely to be reused with in 143 // the system_server process, as that could result in potentially discarding some 144 // non-dreaming_started/stopped related broadcast. 145 // - key needs to be the same for both DREAMING_STARTED and DREAMING_STOPPED broadcasts 146 // so that dreaming_stopped can also clear any older dreaming_started broadcasts that 147 // are yet to be delivered. 148 options.setDeliveryGroupMatchingKey( 149 DREAMING_DELIVERY_GROUP_NAMESPACE, DREAMING_DELIVERY_GROUP_KEY); 150 // This allows the broadcast delivery to be delayed to apps in the Cached state. 151 options.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE); 152 return options.toBundle(); 153 } 154 dump(PrintWriter pw)155 public void dump(PrintWriter pw) { 156 pw.println("Dreamland:"); 157 if (mCurrentDream != null) { 158 pw.println(" mCurrentDream:"); 159 pw.println(" mToken=" + mCurrentDream.mToken); 160 pw.println(" mName=" + mCurrentDream.mName); 161 pw.println(" mIsPreviewMode=" + mCurrentDream.mIsPreviewMode); 162 pw.println(" mCanDoze=" + mCurrentDream.mCanDoze); 163 pw.println(" mUserId=" + mCurrentDream.mUserId); 164 pw.println(" mBound=" + mCurrentDream.mBound); 165 pw.println(" mService=" + mCurrentDream.mService); 166 pw.println(" mWakingGently=" + mCurrentDream.mWakingGently); 167 } else { 168 pw.println(" mCurrentDream: null"); 169 } 170 171 pw.println(" mSentStartBroadcast=" + mSentStartBroadcast); 172 } 173 startDream(Binder token, ComponentName name, boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock, ComponentName overlayComponentName, String reason)174 public void startDream(Binder token, ComponentName name, 175 boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock, 176 ComponentName overlayComponentName, String reason) { 177 Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream"); 178 try { 179 // Close the notification shade. No need to send to all, but better to be explicit. 180 mContext.sendBroadcastAsUser(mCloseNotificationShadeIntent, UserHandle.ALL, 181 null /* receiverPermission */, mCloseNotificationShadeOptions); 182 183 Slog.i(TAG, "Starting dream: name=" + name 184 + ", isPreviewMode=" + isPreviewMode + ", canDoze=" + canDoze 185 + ", userId=" + userId + ", reason='" + reason + "'"); 186 187 final DreamRecord oldDream = mCurrentDream; 188 mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock); 189 if (oldDream != null) { 190 if (Objects.equals(oldDream.mName, mCurrentDream.mName)) { 191 // We are attempting to start a dream that is currently waking up gently. 192 // Let's silently stop the old instance here to clear the dream state. 193 // This should happen after the new mCurrentDream is set to avoid announcing 194 // a "dream stopped" state. 195 stopDreamInstance(/* immediately */ true, "restarting same dream", oldDream); 196 } else { 197 mPreviousDreams.add(oldDream); 198 } 199 } 200 201 mCurrentDream.mDreamStartTime = SystemClock.elapsedRealtime(); 202 MetricsLogger.visible(mContext, 203 mCurrentDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); 204 205 Intent intent = new Intent(DreamService.SERVICE_INTERFACE); 206 intent.setComponent(name); 207 intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 208 DreamService.setDreamOverlayComponent(intent, overlayComponentName); 209 try { 210 if (!mContext.bindServiceAsUser(intent, mCurrentDream, 211 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, 212 new UserHandle(userId))) { 213 Slog.e(TAG, "Unable to bind dream service: " + intent); 214 stopDream(true /*immediate*/, "bindService failed"); 215 return; 216 } 217 } catch (SecurityException ex) { 218 Slog.e(TAG, "Unable to bind dream service: " + intent, ex); 219 stopDream(true /*immediate*/, "unable to bind service: SecExp."); 220 return; 221 } 222 223 mCurrentDream.mBound = true; 224 mHandler.postDelayed(mCurrentDream.mStopUnconnectedDreamRunnable, 225 DREAM_CONNECTION_TIMEOUT); 226 } finally { 227 Trace.traceEnd(Trace.TRACE_TAG_POWER); 228 } 229 } 230 231 /** 232 * Provides an appTask for the dream with token {@code dreamToken}, so that the dream controller 233 * can stop the dream task when necessary. 234 */ setDreamAppTask(Binder dreamToken, IAppTask appTask)235 void setDreamAppTask(Binder dreamToken, IAppTask appTask) { 236 if (mCurrentDream == null || mCurrentDream.mToken != dreamToken 237 || mCurrentDream.mAppTask != null) { 238 Slog.e(TAG, "Illegal dream activity start. mCurrentDream.mToken = " 239 + mCurrentDream.mToken + ", illegal dreamToken = " + dreamToken 240 + ". Ending this dream activity."); 241 try { 242 appTask.finishAndRemoveTask(); 243 } catch (RemoteException | RuntimeException e) { 244 Slog.e(TAG, "Unable to stop illegal dream activity."); 245 } 246 return; 247 } 248 249 mCurrentDream.mAppTask = appTask; 250 } 251 setDreamIsObscured(boolean isObscured)252 void setDreamIsObscured(boolean isObscured) { 253 if (mCurrentDream != null) { 254 mCurrentDream.mDreamIsObscured = isObscured; 255 } 256 } 257 dreamIsFrontmost()258 boolean dreamIsFrontmost() { 259 return mCurrentDream != null && mCurrentDream.dreamIsFrontmost(); 260 } 261 262 /** 263 * Sends a user activity signal to PowerManager to stop the screen from turning off immediately 264 * if there hasn't been any user interaction in a while. 265 */ resetScreenTimeout()266 private void resetScreenTimeout() { 267 Slog.i(TAG, "Resetting screen timeout"); 268 long time = SystemClock.uptimeMillis(); 269 mPowerManager.userActivity(time, USER_ACTIVITY_EVENT_OTHER, 270 USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS); 271 } 272 273 /** 274 * Stops dreaming. 275 * 276 * The current dream, if any, and any unstopped previous dreams are stopped. The device stops 277 * dreaming. 278 */ stopDream(boolean immediate, String reason)279 public void stopDream(boolean immediate, String reason) { 280 stopPreviousDreams(); 281 stopDreamInstance(immediate, reason, mCurrentDream); 282 } 283 bringDreamToFront()284 public boolean bringDreamToFront() { 285 if (mCurrentDream == null || mCurrentDream.mService == null) { 286 return false; 287 } 288 289 try { 290 mCurrentDream.mService.comeToFront(); 291 return true; 292 } catch (RemoteException e) { 293 Slog.e(TAG, "Error asking dream to come to the front", e); 294 } 295 296 return false; 297 } 298 299 /** 300 * Stops the given dream instance. 301 * 302 * The device may still be dreaming afterwards if there are other dreams running. 303 */ stopDreamInstance(boolean immediate, String reason, DreamRecord dream)304 private void stopDreamInstance(boolean immediate, String reason, DreamRecord dream) { 305 if (dream == null) { 306 return; 307 } 308 309 Trace.traceBegin(Trace.TRACE_TAG_POWER, "stopDream"); 310 try { 311 if (!immediate) { 312 if (dream.mWakingGently) { 313 return; // already waking gently 314 } 315 316 if (dream.mService != null) { 317 // Give the dream a moment to wake up and finish itself gently. 318 dream.mWakingGently = true; 319 try { 320 dream.mStopReason = reason; 321 dream.mService.wakeUp(); 322 mHandler.postDelayed(dream.mStopStubbornDreamRunnable, 323 DREAM_FINISH_TIMEOUT); 324 return; 325 } catch (RemoteException ex) { 326 // oh well, we tried, finish immediately instead 327 } 328 } 329 } 330 331 Slog.i(TAG, "Stopping dream: name=" + dream.mName 332 + ", isPreviewMode=" + dream.mIsPreviewMode 333 + ", canDoze=" + dream.mCanDoze 334 + ", userId=" + dream.mUserId 335 + ", reason='" + reason + "'" 336 + (dream.mStopReason == null ? "" : "(from '" 337 + dream.mStopReason + "')")); 338 MetricsLogger.hidden(mContext, 339 dream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); 340 MetricsLogger.histogram(mContext, 341 dream.mCanDoze ? "dozing_minutes" : "dreaming_minutes", 342 (int) ((SystemClock.elapsedRealtime() - dream.mDreamStartTime) / (1000L 343 * 60L))); 344 345 mHandler.removeCallbacks(dream.mStopUnconnectedDreamRunnable); 346 mHandler.removeCallbacks(dream.mStopStubbornDreamRunnable); 347 348 if (dream.mService != null) { 349 try { 350 dream.mService.detach(); 351 } catch (RemoteException ex) { 352 // we don't care; this thing is on the way out 353 } 354 355 try { 356 dream.mService.asBinder().unlinkToDeath(dream, 0); 357 } catch (NoSuchElementException ex) { 358 // don't care 359 } 360 dream.mService = null; 361 } 362 363 if (dream.mBound) { 364 mContext.unbindService(dream); 365 } 366 dream.releaseWakeLockIfNeeded(); 367 368 // Current dream stopped, device no longer dreaming. 369 if (dream == mCurrentDream) { 370 mCurrentDream = null; 371 372 if (mSentStartBroadcast) { 373 mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL, 374 null /* receiverPermission */, mDreamingStartedStoppedOptions); 375 mSentStartBroadcast = false; 376 } 377 378 if (mCurrentDream != null && mCurrentDream.mAppTask != null) { 379 // Finish the dream task in case it hasn't finished by itself already. 380 try { 381 mCurrentDream.mAppTask.finishAndRemoveTask(); 382 } catch (RemoteException | RuntimeException e) { 383 Slog.e(TAG, "Unable to stop dream activity."); 384 } 385 } 386 387 mListener.onDreamStopped(dream.mToken); 388 } 389 390 } finally { 391 Trace.traceEnd(Trace.TRACE_TAG_POWER); 392 } 393 } 394 395 /** 396 * Stops all previous dreams, if any. 397 */ stopPreviousDreams()398 private void stopPreviousDreams() { 399 if (mPreviousDreams.isEmpty()) { 400 return; 401 } 402 403 // Using an iterator because mPreviousDreams is modified while the iteration is in process. 404 for (final Iterator<DreamRecord> it = mPreviousDreams.iterator(); it.hasNext(); ) { 405 stopDreamInstance(true /*immediate*/, "stop previous dream", it.next()); 406 it.remove(); 407 } 408 } 409 attach(IDreamService service)410 private void attach(IDreamService service) { 411 try { 412 service.asBinder().linkToDeath(mCurrentDream, 0); 413 service.attach(mCurrentDream.mToken, mCurrentDream.mCanDoze, 414 mCurrentDream.mIsPreviewMode, mCurrentDream.mDreamingStartedCallback); 415 } catch (RemoteException ex) { 416 Slog.e(TAG, "The dream service died unexpectedly.", ex); 417 stopDream(true /*immediate*/, "attach failed"); 418 return; 419 } 420 421 mCurrentDream.mService = service; 422 423 if (!mCurrentDream.mIsPreviewMode && !mSentStartBroadcast) { 424 mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL, 425 null /* receiverPermission */, mDreamingStartedStoppedOptions); 426 mListener.onDreamStarted(mCurrentDream.mToken); 427 mSentStartBroadcast = true; 428 } 429 } 430 431 /** 432 * Callback interface to be implemented by the {@link DreamManagerService}. 433 */ 434 public interface Listener { onDreamStarted(Binder token)435 void onDreamStarted(Binder token); onDreamStopped(Binder token)436 void onDreamStopped(Binder token); 437 } 438 439 private final class DreamRecord implements DeathRecipient, ServiceConnection { 440 public final Binder mToken; 441 public final ComponentName mName; 442 public final boolean mIsPreviewMode; 443 public final boolean mCanDoze; 444 public final int mUserId; 445 public IAppTask mAppTask; 446 447 public PowerManager.WakeLock mWakeLock; 448 public boolean mBound; 449 public boolean mConnected; 450 public IDreamService mService; 451 private String mStopReason; 452 private long mDreamStartTime; 453 public boolean mWakingGently; 454 private boolean mDreamIsObscured; 455 456 private final Runnable mStopPreviousDreamsIfNeeded = this::stopPreviousDreamsIfNeeded; 457 private final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded; 458 459 private final Runnable mStopUnconnectedDreamRunnable = () -> { 460 if (mBound && !mConnected) { 461 Slog.w(TAG, "Bound dream did not connect in the time allotted"); 462 stopDream(true /*immediate*/, "slow to connect" /*reason*/); 463 } 464 }; 465 466 private final Runnable mStopStubbornDreamRunnable = () -> { 467 Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted"); 468 stopDream(true /*immediate*/, "slow to finish" /*reason*/); 469 mStopReason = null; 470 }; 471 472 private final IRemoteCallback mDreamingStartedCallback = new IRemoteCallback.Stub() { 473 // May be called on any thread. 474 @Override 475 public void sendResult(Bundle data) { 476 mHandler.post(mStopPreviousDreamsIfNeeded); 477 mHandler.post(mReleaseWakeLockIfNeeded); 478 } 479 }; 480 DreamRecord(Binder token, ComponentName name, boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock)481 DreamRecord(Binder token, ComponentName name, boolean isPreviewMode, 482 boolean canDoze, int userId, PowerManager.WakeLock wakeLock) { 483 mToken = token; 484 mName = name; 485 mIsPreviewMode = isPreviewMode; 486 mCanDoze = canDoze; 487 mUserId = userId; 488 mWakeLock = wakeLock; 489 // Hold the lock while we're waiting for the service to connect and start dreaming. 490 // Released after the service has started dreaming, we stop dreaming, or it timed out. 491 if (mWakeLock != null) { 492 mWakeLock.acquire(); 493 } 494 mHandler.postDelayed(mReleaseWakeLockIfNeeded, 10000); 495 } 496 497 // May be called on any thread. 498 @Override binderDied()499 public void binderDied() { 500 mHandler.post(() -> { 501 mService = null; 502 if (mCurrentDream == DreamRecord.this) { 503 if (mResetScreenTimeoutOnUnexpectedDreamExit) { 504 resetScreenTimeout(); 505 } 506 stopDream(true /*immediate*/, "binder died"); 507 } 508 }); 509 } 510 511 // May be called on any thread. 512 @Override onServiceConnected(ComponentName name, final IBinder service)513 public void onServiceConnected(ComponentName name, final IBinder service) { 514 mHandler.post(() -> { 515 mConnected = true; 516 if (mCurrentDream == DreamRecord.this && mService == null) { 517 attach(IDreamService.Stub.asInterface(service)); 518 // Wake lock will be released once dreaming starts. 519 } else { 520 releaseWakeLockIfNeeded(); 521 } 522 }); 523 } 524 525 // May be called on any thread. 526 @Override onServiceDisconnected(ComponentName name)527 public void onServiceDisconnected(ComponentName name) { 528 mHandler.post(() -> { 529 mService = null; 530 if (mCurrentDream == DreamRecord.this) { 531 if (mResetScreenTimeoutOnUnexpectedDreamExit) { 532 resetScreenTimeout(); 533 } 534 stopDream(true /*immediate*/, "service disconnected"); 535 } 536 }); 537 } 538 stopPreviousDreamsIfNeeded()539 void stopPreviousDreamsIfNeeded() { 540 if (mCurrentDream == DreamRecord.this) { 541 stopPreviousDreams(); 542 } 543 } 544 releaseWakeLockIfNeeded()545 void releaseWakeLockIfNeeded() { 546 if (mWakeLock != null) { 547 mWakeLock.release(); 548 mWakeLock = null; 549 mHandler.removeCallbacks(mReleaseWakeLockIfNeeded); 550 } 551 } 552 dreamIsFrontmost()553 boolean dreamIsFrontmost() { 554 return !mDreamIsObscured; 555 } 556 } 557 } 558