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