• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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