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 android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.ServiceConnection; 23 import android.os.Binder; 24 import android.os.Bundle; 25 import android.os.Handler; 26 import android.os.IBinder; 27 import android.os.IBinder.DeathRecipient; 28 import android.os.IRemoteCallback; 29 import android.os.PowerManager; 30 import android.os.RemoteException; 31 import android.os.SystemClock; 32 import android.os.Trace; 33 import android.os.UserHandle; 34 import android.service.dreams.DreamService; 35 import android.service.dreams.IDreamService; 36 import android.util.Slog; 37 38 import com.android.internal.logging.MetricsLogger; 39 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 40 41 import java.io.PrintWriter; 42 import java.util.ArrayList; 43 import java.util.Iterator; 44 import java.util.NoSuchElementException; 45 import java.util.Objects; 46 47 /** 48 * Internal controller for starting and stopping the current dream and managing related state. 49 * 50 * Assumes all operations are called from the dream handler thread. 51 */ 52 final class DreamController { 53 private static final String TAG = "DreamController"; 54 55 // How long we wait for a newly bound dream to create the service connection 56 private static final int DREAM_CONNECTION_TIMEOUT = 5 * 1000; 57 58 // Time to allow the dream to perform an exit transition when waking up. 59 private static final int DREAM_FINISH_TIMEOUT = 5 * 1000; 60 61 private final Context mContext; 62 private final Handler mHandler; 63 private final Listener mListener; 64 65 private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED) 66 .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 67 private final Intent mDreamingStoppedIntent = new Intent(Intent.ACTION_DREAMING_STOPPED) 68 .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 69 70 private final Intent mCloseNotificationShadeIntent; 71 72 private DreamRecord mCurrentDream; 73 74 // Whether a dreaming started intent has been broadcast. 75 private boolean mSentStartBroadcast = false; 76 77 // When a new dream is started and there is an existing dream, the existing dream is allowed to 78 // live a little longer until the new dream is started, for a smoother transition. This dream is 79 // stopped as soon as the new dream is started, and this list is cleared. Usually there should 80 // only be one previous dream while waiting for a new dream to start, but we store a list to 81 // proof the edge case of multiple previous dreams. 82 private final ArrayList<DreamRecord> mPreviousDreams = new ArrayList<>(); 83 DreamController(Context context, Handler handler, Listener listener)84 public DreamController(Context context, Handler handler, Listener listener) { 85 mContext = context; 86 mHandler = handler; 87 mListener = listener; 88 mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 89 mCloseNotificationShadeIntent.putExtra("reason", "dream"); 90 } 91 dump(PrintWriter pw)92 public void dump(PrintWriter pw) { 93 pw.println("Dreamland:"); 94 if (mCurrentDream != null) { 95 pw.println(" mCurrentDream:"); 96 pw.println(" mToken=" + mCurrentDream.mToken); 97 pw.println(" mName=" + mCurrentDream.mName); 98 pw.println(" mIsPreviewMode=" + mCurrentDream.mIsPreviewMode); 99 pw.println(" mCanDoze=" + mCurrentDream.mCanDoze); 100 pw.println(" mUserId=" + mCurrentDream.mUserId); 101 pw.println(" mBound=" + mCurrentDream.mBound); 102 pw.println(" mService=" + mCurrentDream.mService); 103 pw.println(" mWakingGently=" + mCurrentDream.mWakingGently); 104 } else { 105 pw.println(" mCurrentDream: null"); 106 } 107 108 pw.println(" mSentStartBroadcast=" + mSentStartBroadcast); 109 } 110 startDream(Binder token, ComponentName name, boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock, ComponentName overlayComponentName, String reason)111 public void startDream(Binder token, ComponentName name, 112 boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock, 113 ComponentName overlayComponentName, String reason) { 114 Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream"); 115 try { 116 // Close the notification shade. No need to send to all, but better to be explicit. 117 mContext.sendBroadcastAsUser(mCloseNotificationShadeIntent, UserHandle.ALL); 118 119 Slog.i(TAG, "Starting dream: name=" + name 120 + ", isPreviewMode=" + isPreviewMode + ", canDoze=" + canDoze 121 + ", userId=" + userId + ", reason='" + reason + "'"); 122 123 final DreamRecord oldDream = mCurrentDream; 124 mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock); 125 if (oldDream != null) { 126 if (Objects.equals(oldDream.mName, mCurrentDream.mName)) { 127 // We are attempting to start a dream that is currently waking up gently. 128 // Let's silently stop the old instance here to clear the dream state. 129 // This should happen after the new mCurrentDream is set to avoid announcing 130 // a "dream stopped" state. 131 stopDreamInstance(/* immediately */ true, "restarting same dream", oldDream); 132 } else { 133 mPreviousDreams.add(oldDream); 134 } 135 } 136 137 mCurrentDream.mDreamStartTime = SystemClock.elapsedRealtime(); 138 MetricsLogger.visible(mContext, 139 mCurrentDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); 140 141 Intent intent = new Intent(DreamService.SERVICE_INTERFACE); 142 intent.setComponent(name); 143 intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 144 intent.putExtra(DreamService.EXTRA_DREAM_OVERLAY_COMPONENT, overlayComponentName); 145 try { 146 if (!mContext.bindServiceAsUser(intent, mCurrentDream, 147 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, 148 new UserHandle(userId))) { 149 Slog.e(TAG, "Unable to bind dream service: " + intent); 150 stopDream(true /*immediate*/, "bindService failed"); 151 return; 152 } 153 } catch (SecurityException ex) { 154 Slog.e(TAG, "Unable to bind dream service: " + intent, ex); 155 stopDream(true /*immediate*/, "unable to bind service: SecExp."); 156 return; 157 } 158 159 mCurrentDream.mBound = true; 160 mHandler.postDelayed(mCurrentDream.mStopUnconnectedDreamRunnable, 161 DREAM_CONNECTION_TIMEOUT); 162 } finally { 163 Trace.traceEnd(Trace.TRACE_TAG_POWER); 164 } 165 } 166 167 /** 168 * Stops dreaming. 169 * 170 * The current dream, if any, and any unstopped previous dreams are stopped. The device stops 171 * dreaming. 172 */ stopDream(boolean immediate, String reason)173 public void stopDream(boolean immediate, String reason) { 174 stopPreviousDreams(); 175 stopDreamInstance(immediate, reason, mCurrentDream); 176 } 177 178 /** 179 * Stops the given dream instance. 180 * 181 * The device may still be dreaming afterwards if there are other dreams running. 182 */ stopDreamInstance(boolean immediate, String reason, DreamRecord dream)183 private void stopDreamInstance(boolean immediate, String reason, DreamRecord dream) { 184 if (dream == null) { 185 return; 186 } 187 188 Trace.traceBegin(Trace.TRACE_TAG_POWER, "stopDream"); 189 try { 190 if (!immediate) { 191 if (dream.mWakingGently) { 192 return; // already waking gently 193 } 194 195 if (dream.mService != null) { 196 // Give the dream a moment to wake up and finish itself gently. 197 dream.mWakingGently = true; 198 try { 199 dream.mStopReason = reason; 200 dream.mService.wakeUp(); 201 mHandler.postDelayed(dream.mStopStubbornDreamRunnable, 202 DREAM_FINISH_TIMEOUT); 203 return; 204 } catch (RemoteException ex) { 205 // oh well, we tried, finish immediately instead 206 } 207 } 208 } 209 210 Slog.i(TAG, "Stopping dream: name=" + dream.mName 211 + ", isPreviewMode=" + dream.mIsPreviewMode 212 + ", canDoze=" + dream.mCanDoze 213 + ", userId=" + dream.mUserId 214 + ", reason='" + reason + "'" 215 + (dream.mStopReason == null ? "" : "(from '" 216 + dream.mStopReason + "')")); 217 MetricsLogger.hidden(mContext, 218 dream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); 219 MetricsLogger.histogram(mContext, 220 dream.mCanDoze ? "dozing_minutes" : "dreaming_minutes", 221 (int) ((SystemClock.elapsedRealtime() - dream.mDreamStartTime) / (1000L 222 * 60L))); 223 224 mHandler.removeCallbacks(dream.mStopUnconnectedDreamRunnable); 225 mHandler.removeCallbacks(dream.mStopStubbornDreamRunnable); 226 227 if (dream.mService != null) { 228 try { 229 dream.mService.detach(); 230 } catch (RemoteException ex) { 231 // we don't care; this thing is on the way out 232 } 233 234 try { 235 dream.mService.asBinder().unlinkToDeath(dream, 0); 236 } catch (NoSuchElementException ex) { 237 // don't care 238 } 239 dream.mService = null; 240 } 241 242 if (dream.mBound) { 243 mContext.unbindService(dream); 244 } 245 dream.releaseWakeLockIfNeeded(); 246 247 // Current dream stopped, device no longer dreaming. 248 if (dream == mCurrentDream) { 249 mCurrentDream = null; 250 251 if (mSentStartBroadcast) { 252 mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL); 253 mSentStartBroadcast = false; 254 } 255 256 mListener.onDreamStopped(dream.mToken); 257 } 258 259 } finally { 260 Trace.traceEnd(Trace.TRACE_TAG_POWER); 261 } 262 } 263 264 /** 265 * Stops all previous dreams, if any. 266 */ stopPreviousDreams()267 private void stopPreviousDreams() { 268 if (mPreviousDreams.isEmpty()) { 269 return; 270 } 271 272 // Using an iterator because mPreviousDreams is modified while the iteration is in process. 273 for (final Iterator<DreamRecord> it = mPreviousDreams.iterator(); it.hasNext(); ) { 274 stopDreamInstance(true /*immediate*/, "stop previous dream", it.next()); 275 it.remove(); 276 } 277 } 278 attach(IDreamService service)279 private void attach(IDreamService service) { 280 try { 281 service.asBinder().linkToDeath(mCurrentDream, 0); 282 service.attach(mCurrentDream.mToken, mCurrentDream.mCanDoze, 283 mCurrentDream.mIsPreviewMode, mCurrentDream.mDreamingStartedCallback); 284 } catch (RemoteException ex) { 285 Slog.e(TAG, "The dream service died unexpectedly.", ex); 286 stopDream(true /*immediate*/, "attach failed"); 287 return; 288 } 289 290 mCurrentDream.mService = service; 291 292 if (!mCurrentDream.mIsPreviewMode && !mSentStartBroadcast) { 293 mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL); 294 mSentStartBroadcast = true; 295 } 296 } 297 298 /** 299 * Callback interface to be implemented by the {@link DreamManagerService}. 300 */ 301 public interface Listener { onDreamStopped(Binder token)302 void onDreamStopped(Binder token); 303 } 304 305 private final class DreamRecord implements DeathRecipient, ServiceConnection { 306 public final Binder mToken; 307 public final ComponentName mName; 308 public final boolean mIsPreviewMode; 309 public final boolean mCanDoze; 310 public final int mUserId; 311 312 public PowerManager.WakeLock mWakeLock; 313 public boolean mBound; 314 public boolean mConnected; 315 public IDreamService mService; 316 private String mStopReason; 317 private long mDreamStartTime; 318 public boolean mWakingGently; 319 320 private final Runnable mStopPreviousDreamsIfNeeded = this::stopPreviousDreamsIfNeeded; 321 private final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded; 322 323 private final Runnable mStopUnconnectedDreamRunnable = () -> { 324 if (mBound && !mConnected) { 325 Slog.w(TAG, "Bound dream did not connect in the time allotted"); 326 stopDream(true /*immediate*/, "slow to connect" /*reason*/); 327 } 328 }; 329 330 private final Runnable mStopStubbornDreamRunnable = () -> { 331 Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted"); 332 stopDream(true /*immediate*/, "slow to finish" /*reason*/); 333 mStopReason = null; 334 }; 335 336 private final IRemoteCallback mDreamingStartedCallback = new IRemoteCallback.Stub() { 337 // May be called on any thread. 338 @Override 339 public void sendResult(Bundle data) { 340 mHandler.post(mStopPreviousDreamsIfNeeded); 341 mHandler.post(mReleaseWakeLockIfNeeded); 342 } 343 }; 344 DreamRecord(Binder token, ComponentName name, boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock)345 DreamRecord(Binder token, ComponentName name, boolean isPreviewMode, 346 boolean canDoze, int userId, PowerManager.WakeLock wakeLock) { 347 mToken = token; 348 mName = name; 349 mIsPreviewMode = isPreviewMode; 350 mCanDoze = canDoze; 351 mUserId = userId; 352 mWakeLock = wakeLock; 353 // Hold the lock while we're waiting for the service to connect and start dreaming. 354 // Released after the service has started dreaming, we stop dreaming, or it timed out. 355 if (mWakeLock != null) { 356 mWakeLock.acquire(); 357 } 358 mHandler.postDelayed(mReleaseWakeLockIfNeeded, 10000); 359 } 360 361 // May be called on any thread. 362 @Override binderDied()363 public void binderDied() { 364 mHandler.post(() -> { 365 mService = null; 366 if (mCurrentDream == DreamRecord.this) { 367 stopDream(true /*immediate*/, "binder died"); 368 } 369 }); 370 } 371 372 // May be called on any thread. 373 @Override onServiceConnected(ComponentName name, final IBinder service)374 public void onServiceConnected(ComponentName name, final IBinder service) { 375 mHandler.post(() -> { 376 mConnected = true; 377 if (mCurrentDream == DreamRecord.this && mService == null) { 378 attach(IDreamService.Stub.asInterface(service)); 379 // Wake lock will be released once dreaming starts. 380 } else { 381 releaseWakeLockIfNeeded(); 382 } 383 }); 384 } 385 386 // May be called on any thread. 387 @Override onServiceDisconnected(ComponentName name)388 public void onServiceDisconnected(ComponentName name) { 389 mHandler.post(() -> { 390 mService = null; 391 if (mCurrentDream == DreamRecord.this) { 392 stopDream(true /*immediate*/, "service disconnected"); 393 } 394 }); 395 } 396 stopPreviousDreamsIfNeeded()397 void stopPreviousDreamsIfNeeded() { 398 if (mCurrentDream == DreamRecord.this) { 399 stopPreviousDreams(); 400 } 401 } 402 releaseWakeLockIfNeeded()403 void releaseWakeLockIfNeeded() { 404 if (mWakeLock != null) { 405 mWakeLock.release(); 406 mWakeLock = null; 407 mHandler.removeCallbacks(mReleaseWakeLockIfNeeded); 408 } 409 } 410 } 411 } 412