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