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