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