• 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 com.android.internal.logging.MetricsLogger;
20 import com.android.internal.logging.MetricsProto.MetricsEvent;
21 
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.ServiceConnection;
26 import android.os.Binder;
27 import android.os.Handler;
28 import android.os.IBinder;
29 import android.os.RemoteException;
30 import android.os.IBinder.DeathRecipient;
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 import android.view.IWindowManager;
38 import android.view.WindowManager;
39 import android.view.WindowManagerGlobal;
40 
41 import java.io.PrintWriter;
42 import java.util.NoSuchElementException;
43 
44 /**
45  * Internal controller for starting and stopping the current dream and managing related state.
46  *
47  * Assumes all operations are called from the dream handler thread.
48  */
49 final class DreamController {
50     private static final String TAG = "DreamController";
51 
52     // How long we wait for a newly bound dream to create the service connection
53     private static final int DREAM_CONNECTION_TIMEOUT = 5 * 1000;
54 
55     // Time to allow the dream to perform an exit transition when waking up.
56     private static final int DREAM_FINISH_TIMEOUT = 5 * 1000;
57 
58     private final Context mContext;
59     private final Handler mHandler;
60     private final Listener mListener;
61     private final IWindowManager mIWindowManager;
62     private long mDreamStartTime;
63 
64     private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED)
65             .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
66     private final Intent mDreamingStoppedIntent = new Intent(Intent.ACTION_DREAMING_STOPPED)
67             .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
68 
69     private final Intent mCloseNotificationShadeIntent;
70 
71     private DreamRecord mCurrentDream;
72 
73     private final Runnable mStopUnconnectedDreamRunnable = new Runnable() {
74         @Override
75         public void run() {
76             if (mCurrentDream != null && mCurrentDream.mBound && !mCurrentDream.mConnected) {
77                 Slog.w(TAG, "Bound dream did not connect in the time allotted");
78                 stopDream(true /*immediate*/);
79             }
80         }
81     };
82 
83     private final Runnable mStopStubbornDreamRunnable = new Runnable() {
84         @Override
85         public void run() {
86             Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted");
87             stopDream(true /*immediate*/);
88         }
89     };
90 
DreamController(Context context, Handler handler, Listener listener)91     public DreamController(Context context, Handler handler, Listener listener) {
92         mContext = context;
93         mHandler = handler;
94         mListener = listener;
95         mIWindowManager = WindowManagerGlobal.getWindowManagerService();
96         mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
97         mCloseNotificationShadeIntent.putExtra("reason", "dream");
98     }
99 
dump(PrintWriter pw)100     public void dump(PrintWriter pw) {
101         pw.println("Dreamland:");
102         if (mCurrentDream != null) {
103             pw.println("  mCurrentDream:");
104             pw.println("    mToken=" + mCurrentDream.mToken);
105             pw.println("    mName=" + mCurrentDream.mName);
106             pw.println("    mIsTest=" + mCurrentDream.mIsTest);
107             pw.println("    mCanDoze=" + mCurrentDream.mCanDoze);
108             pw.println("    mUserId=" + mCurrentDream.mUserId);
109             pw.println("    mBound=" + mCurrentDream.mBound);
110             pw.println("    mService=" + mCurrentDream.mService);
111             pw.println("    mSentStartBroadcast=" + mCurrentDream.mSentStartBroadcast);
112             pw.println("    mWakingGently=" + mCurrentDream.mWakingGently);
113         } else {
114             pw.println("  mCurrentDream: null");
115         }
116     }
117 
startDream(Binder token, ComponentName name, boolean isTest, boolean canDoze, int userId)118     public void startDream(Binder token, ComponentName name,
119             boolean isTest, boolean canDoze, int userId) {
120         stopDream(true /*immediate*/);
121 
122         Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream");
123         try {
124             // Close the notification shade. Don't need to send to all, but better to be explicit.
125             mContext.sendBroadcastAsUser(mCloseNotificationShadeIntent, UserHandle.ALL);
126 
127             Slog.i(TAG, "Starting dream: name=" + name
128                     + ", isTest=" + isTest + ", canDoze=" + canDoze
129                     + ", userId=" + userId);
130 
131             mCurrentDream = new DreamRecord(token, name, isTest, canDoze, userId);
132 
133             mDreamStartTime = SystemClock.elapsedRealtime();
134             MetricsLogger.visible(mContext,
135                     mCurrentDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING);
136 
137             try {
138                 mIWindowManager.addWindowToken(token, WindowManager.LayoutParams.TYPE_DREAM);
139             } catch (RemoteException ex) {
140                 Slog.e(TAG, "Unable to add window token for dream.", ex);
141                 stopDream(true /*immediate*/);
142                 return;
143             }
144 
145             Intent intent = new Intent(DreamService.SERVICE_INTERFACE);
146             intent.setComponent(name);
147             intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
148             try {
149                 if (!mContext.bindServiceAsUser(intent, mCurrentDream,
150                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
151                         new UserHandle(userId))) {
152                     Slog.e(TAG, "Unable to bind dream service: " + intent);
153                     stopDream(true /*immediate*/);
154                     return;
155                 }
156             } catch (SecurityException ex) {
157                 Slog.e(TAG, "Unable to bind dream service: " + intent, ex);
158                 stopDream(true /*immediate*/);
159                 return;
160             }
161 
162             mCurrentDream.mBound = true;
163             mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT);
164         } finally {
165             Trace.traceEnd(Trace.TRACE_TAG_POWER);
166         }
167     }
168 
stopDream(boolean immediate)169     public void stopDream(boolean immediate) {
170         if (mCurrentDream == null) {
171             return;
172         }
173 
174         Trace.traceBegin(Trace.TRACE_TAG_POWER, "stopDream");
175         try {
176             if (!immediate) {
177                 if (mCurrentDream.mWakingGently) {
178                     return; // already waking gently
179                 }
180 
181                 if (mCurrentDream.mService != null) {
182                     // Give the dream a moment to wake up and finish itself gently.
183                     mCurrentDream.mWakingGently = true;
184                     try {
185                         mCurrentDream.mService.wakeUp();
186                         mHandler.postDelayed(mStopStubbornDreamRunnable, DREAM_FINISH_TIMEOUT);
187                         return;
188                     } catch (RemoteException ex) {
189                         // oh well, we tried, finish immediately instead
190                     }
191                 }
192             }
193 
194             final DreamRecord oldDream = mCurrentDream;
195             mCurrentDream = null;
196             Slog.i(TAG, "Stopping dream: name=" + oldDream.mName
197                     + ", isTest=" + oldDream.mIsTest + ", canDoze=" + oldDream.mCanDoze
198                     + ", userId=" + oldDream.mUserId);
199             MetricsLogger.hidden(mContext,
200                     oldDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING);
201             MetricsLogger.histogram(mContext,
202                     oldDream.mCanDoze ? "dozing_minutes" : "dreaming_minutes" ,
203                     (int) ((SystemClock.elapsedRealtime() - mDreamStartTime) / (1000L * 60L)));
204 
205             mHandler.removeCallbacks(mStopUnconnectedDreamRunnable);
206             mHandler.removeCallbacks(mStopStubbornDreamRunnable);
207 
208             if (oldDream.mSentStartBroadcast) {
209                 mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL);
210             }
211 
212             if (oldDream.mService != null) {
213                 // Tell the dream that it's being stopped so that
214                 // it can shut down nicely before we yank its window token out from
215                 // under it.
216                 try {
217                     oldDream.mService.detach();
218                 } catch (RemoteException ex) {
219                     // we don't care; this thing is on the way out
220                 }
221 
222                 try {
223                     oldDream.mService.asBinder().unlinkToDeath(oldDream, 0);
224                 } catch (NoSuchElementException ex) {
225                     // don't care
226                 }
227                 oldDream.mService = null;
228             }
229 
230             if (oldDream.mBound) {
231                 mContext.unbindService(oldDream);
232             }
233 
234             try {
235                 mIWindowManager.removeWindowToken(oldDream.mToken);
236             } catch (RemoteException ex) {
237                 Slog.w(TAG, "Error removing window token for dream.", ex);
238             }
239 
240             mHandler.post(new Runnable() {
241                 @Override
242                 public void run() {
243                     mListener.onDreamStopped(oldDream.mToken);
244                 }
245             });
246         } finally {
247             Trace.traceEnd(Trace.TRACE_TAG_POWER);
248         }
249     }
250 
attach(IDreamService service)251     private void attach(IDreamService service) {
252         try {
253             service.asBinder().linkToDeath(mCurrentDream, 0);
254             service.attach(mCurrentDream.mToken, mCurrentDream.mCanDoze);
255         } catch (RemoteException ex) {
256             Slog.e(TAG, "The dream service died unexpectedly.", ex);
257             stopDream(true /*immediate*/);
258             return;
259         }
260 
261         mCurrentDream.mService = service;
262 
263         if (!mCurrentDream.mIsTest) {
264             mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL);
265             mCurrentDream.mSentStartBroadcast = true;
266         }
267     }
268 
269     /**
270      * Callback interface to be implemented by the {@link DreamManagerService}.
271      */
272     public interface Listener {
onDreamStopped(Binder token)273         void onDreamStopped(Binder token);
274     }
275 
276     private final class DreamRecord implements DeathRecipient, ServiceConnection {
277         public final Binder mToken;
278         public final ComponentName mName;
279         public final boolean mIsTest;
280         public final boolean mCanDoze;
281         public final int mUserId;
282 
283         public boolean mBound;
284         public boolean mConnected;
285         public IDreamService mService;
286         public boolean mSentStartBroadcast;
287 
288         public boolean mWakingGently;
289 
DreamRecord(Binder token, ComponentName name, boolean isTest, boolean canDoze, int userId)290         public DreamRecord(Binder token, ComponentName name,
291                 boolean isTest, boolean canDoze, int userId) {
292             mToken = token;
293             mName = name;
294             mIsTest = isTest;
295             mCanDoze = canDoze;
296             mUserId  = userId;
297         }
298 
299         // May be called on any thread.
300         @Override
binderDied()301         public void binderDied() {
302             mHandler.post(new Runnable() {
303                 @Override
304                 public void run() {
305                     mService = null;
306                     if (mCurrentDream == DreamRecord.this) {
307                         stopDream(true /*immediate*/);
308                     }
309                 }
310             });
311         }
312 
313         // May be called on any thread.
314         @Override
onServiceConnected(ComponentName name, final IBinder service)315         public void onServiceConnected(ComponentName name, final IBinder service) {
316             mHandler.post(new Runnable() {
317                 @Override
318                 public void run() {
319                     mConnected = true;
320                     if (mCurrentDream == DreamRecord.this && mService == null) {
321                         attach(IDreamService.Stub.asInterface(service));
322                     }
323                 }
324             });
325         }
326 
327         // May be called on any thread.
328         @Override
onServiceDisconnected(ComponentName name)329         public void onServiceDisconnected(ComponentName name) {
330             mHandler.post(new Runnable() {
331                 @Override
332                 public void run() {
333                     mService = null;
334                     if (mCurrentDream == DreamRecord.this) {
335                         stopDream(true /*immediate*/);
336                     }
337                 }
338             });
339         }
340     }
341 }