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