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 intent.putExtra(DreamService.EXTRA_DREAM_OVERLAY_COMPONENT, 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 252 /** 253 * Sends a user activity signal to PowerManager to stop the screen from turning off immediately 254 * if there hasn't been any user interaction in a while. 255 */ resetScreenTimeout()256 private void resetScreenTimeout() { 257 Slog.i(TAG, "Resetting screen timeout"); 258 long time = SystemClock.uptimeMillis(); 259 mPowerManager.userActivity(time, USER_ACTIVITY_EVENT_OTHER, 260 USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS); 261 } 262 263 /** 264 * Stops dreaming. 265 * 266 * The current dream, if any, and any unstopped previous dreams are stopped. The device stops 267 * dreaming. 268 */ stopDream(boolean immediate, String reason)269 public void stopDream(boolean immediate, String reason) { 270 stopPreviousDreams(); 271 stopDreamInstance(immediate, reason, mCurrentDream); 272 } 273 274 /** 275 * Stops the given dream instance. 276 * 277 * The device may still be dreaming afterwards if there are other dreams running. 278 */ stopDreamInstance(boolean immediate, String reason, DreamRecord dream)279 private void stopDreamInstance(boolean immediate, String reason, DreamRecord dream) { 280 if (dream == null) { 281 return; 282 } 283 284 Trace.traceBegin(Trace.TRACE_TAG_POWER, "stopDream"); 285 try { 286 if (!immediate) { 287 if (dream.mWakingGently) { 288 return; // already waking gently 289 } 290 291 if (dream.mService != null) { 292 // Give the dream a moment to wake up and finish itself gently. 293 dream.mWakingGently = true; 294 try { 295 dream.mStopReason = reason; 296 dream.mService.wakeUp(); 297 mHandler.postDelayed(dream.mStopStubbornDreamRunnable, 298 DREAM_FINISH_TIMEOUT); 299 return; 300 } catch (RemoteException ex) { 301 // oh well, we tried, finish immediately instead 302 } 303 } 304 } 305 306 Slog.i(TAG, "Stopping dream: name=" + dream.mName 307 + ", isPreviewMode=" + dream.mIsPreviewMode 308 + ", canDoze=" + dream.mCanDoze 309 + ", userId=" + dream.mUserId 310 + ", reason='" + reason + "'" 311 + (dream.mStopReason == null ? "" : "(from '" 312 + dream.mStopReason + "')")); 313 MetricsLogger.hidden(mContext, 314 dream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); 315 MetricsLogger.histogram(mContext, 316 dream.mCanDoze ? "dozing_minutes" : "dreaming_minutes", 317 (int) ((SystemClock.elapsedRealtime() - dream.mDreamStartTime) / (1000L 318 * 60L))); 319 320 mHandler.removeCallbacks(dream.mStopUnconnectedDreamRunnable); 321 mHandler.removeCallbacks(dream.mStopStubbornDreamRunnable); 322 323 if (dream.mService != null) { 324 try { 325 dream.mService.detach(); 326 } catch (RemoteException ex) { 327 // we don't care; this thing is on the way out 328 } 329 330 try { 331 dream.mService.asBinder().unlinkToDeath(dream, 0); 332 } catch (NoSuchElementException ex) { 333 // don't care 334 } 335 dream.mService = null; 336 } 337 338 if (dream.mBound) { 339 mContext.unbindService(dream); 340 } 341 dream.releaseWakeLockIfNeeded(); 342 343 // Current dream stopped, device no longer dreaming. 344 if (dream == mCurrentDream) { 345 mCurrentDream = null; 346 347 if (mSentStartBroadcast) { 348 mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL, 349 null /* receiverPermission */, mDreamingStartedStoppedOptions); 350 mSentStartBroadcast = false; 351 } 352 353 if (mCurrentDream != null && mCurrentDream.mAppTask != null) { 354 // Finish the dream task in case it hasn't finished by itself already. 355 try { 356 mCurrentDream.mAppTask.finishAndRemoveTask(); 357 } catch (RemoteException | RuntimeException e) { 358 Slog.e(TAG, "Unable to stop dream activity."); 359 } 360 } 361 362 mListener.onDreamStopped(dream.mToken); 363 } 364 365 } finally { 366 Trace.traceEnd(Trace.TRACE_TAG_POWER); 367 } 368 } 369 370 /** 371 * Stops all previous dreams, if any. 372 */ stopPreviousDreams()373 private void stopPreviousDreams() { 374 if (mPreviousDreams.isEmpty()) { 375 return; 376 } 377 378 // Using an iterator because mPreviousDreams is modified while the iteration is in process. 379 for (final Iterator<DreamRecord> it = mPreviousDreams.iterator(); it.hasNext(); ) { 380 stopDreamInstance(true /*immediate*/, "stop previous dream", it.next()); 381 it.remove(); 382 } 383 } 384 attach(IDreamService service)385 private void attach(IDreamService service) { 386 try { 387 service.asBinder().linkToDeath(mCurrentDream, 0); 388 service.attach(mCurrentDream.mToken, mCurrentDream.mCanDoze, 389 mCurrentDream.mIsPreviewMode, mCurrentDream.mDreamingStartedCallback); 390 } catch (RemoteException ex) { 391 Slog.e(TAG, "The dream service died unexpectedly.", ex); 392 stopDream(true /*immediate*/, "attach failed"); 393 return; 394 } 395 396 mCurrentDream.mService = service; 397 398 if (!mCurrentDream.mIsPreviewMode && !mSentStartBroadcast) { 399 mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL, 400 null /* receiverPermission */, mDreamingStartedStoppedOptions); 401 mListener.onDreamStarted(mCurrentDream.mToken); 402 mSentStartBroadcast = true; 403 } 404 } 405 406 /** 407 * Callback interface to be implemented by the {@link DreamManagerService}. 408 */ 409 public interface Listener { onDreamStarted(Binder token)410 void onDreamStarted(Binder token); onDreamStopped(Binder token)411 void onDreamStopped(Binder token); 412 } 413 414 private final class DreamRecord implements DeathRecipient, ServiceConnection { 415 public final Binder mToken; 416 public final ComponentName mName; 417 public final boolean mIsPreviewMode; 418 public final boolean mCanDoze; 419 public final int mUserId; 420 public IAppTask mAppTask; 421 422 public PowerManager.WakeLock mWakeLock; 423 public boolean mBound; 424 public boolean mConnected; 425 public IDreamService mService; 426 private String mStopReason; 427 private long mDreamStartTime; 428 public boolean mWakingGently; 429 430 private final Runnable mStopPreviousDreamsIfNeeded = this::stopPreviousDreamsIfNeeded; 431 private final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded; 432 433 private final Runnable mStopUnconnectedDreamRunnable = () -> { 434 if (mBound && !mConnected) { 435 Slog.w(TAG, "Bound dream did not connect in the time allotted"); 436 stopDream(true /*immediate*/, "slow to connect" /*reason*/); 437 } 438 }; 439 440 private final Runnable mStopStubbornDreamRunnable = () -> { 441 Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted"); 442 stopDream(true /*immediate*/, "slow to finish" /*reason*/); 443 mStopReason = null; 444 }; 445 446 private final IRemoteCallback mDreamingStartedCallback = new IRemoteCallback.Stub() { 447 // May be called on any thread. 448 @Override 449 public void sendResult(Bundle data) { 450 mHandler.post(mStopPreviousDreamsIfNeeded); 451 mHandler.post(mReleaseWakeLockIfNeeded); 452 } 453 }; 454 DreamRecord(Binder token, ComponentName name, boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock)455 DreamRecord(Binder token, ComponentName name, boolean isPreviewMode, 456 boolean canDoze, int userId, PowerManager.WakeLock wakeLock) { 457 mToken = token; 458 mName = name; 459 mIsPreviewMode = isPreviewMode; 460 mCanDoze = canDoze; 461 mUserId = userId; 462 mWakeLock = wakeLock; 463 // Hold the lock while we're waiting for the service to connect and start dreaming. 464 // Released after the service has started dreaming, we stop dreaming, or it timed out. 465 if (mWakeLock != null) { 466 mWakeLock.acquire(); 467 } 468 mHandler.postDelayed(mReleaseWakeLockIfNeeded, 10000); 469 } 470 471 // May be called on any thread. 472 @Override binderDied()473 public void binderDied() { 474 mHandler.post(() -> { 475 mService = null; 476 if (mCurrentDream == DreamRecord.this) { 477 if (mResetScreenTimeoutOnUnexpectedDreamExit) { 478 resetScreenTimeout(); 479 } 480 stopDream(true /*immediate*/, "binder died"); 481 } 482 }); 483 } 484 485 // May be called on any thread. 486 @Override onServiceConnected(ComponentName name, final IBinder service)487 public void onServiceConnected(ComponentName name, final IBinder service) { 488 mHandler.post(() -> { 489 mConnected = true; 490 if (mCurrentDream == DreamRecord.this && mService == null) { 491 attach(IDreamService.Stub.asInterface(service)); 492 // Wake lock will be released once dreaming starts. 493 } else { 494 releaseWakeLockIfNeeded(); 495 } 496 }); 497 } 498 499 // May be called on any thread. 500 @Override onServiceDisconnected(ComponentName name)501 public void onServiceDisconnected(ComponentName name) { 502 mHandler.post(() -> { 503 mService = null; 504 if (mCurrentDream == DreamRecord.this) { 505 if (mResetScreenTimeoutOnUnexpectedDreamExit) { 506 resetScreenTimeout(); 507 } 508 stopDream(true /*immediate*/, "service disconnected"); 509 } 510 }); 511 } 512 stopPreviousDreamsIfNeeded()513 void stopPreviousDreamsIfNeeded() { 514 if (mCurrentDream == DreamRecord.this) { 515 stopPreviousDreams(); 516 } 517 } 518 releaseWakeLockIfNeeded()519 void releaseWakeLockIfNeeded() { 520 if (mWakeLock != null) { 521 mWakeLock.release(); 522 mWakeLock = null; 523 mHandler.removeCallbacks(mReleaseWakeLockIfNeeded); 524 } 525 } 526 } 527 } 528