DreamController.java revision d69e4c1460017062e7c36be55801cb434ad19d97
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 | Context.BIND_FOREGROUND_SERVICE,
140                        new UserHandle(userId))) {
141                    Slog.e(TAG, "Unable to bind dream service: " + intent);
142                    stopDream(true /*immediate*/);
143                    return;
144                }
145            } catch (SecurityException ex) {
146                Slog.e(TAG, "Unable to bind dream service: " + intent, ex);
147                stopDream(true /*immediate*/);
148                return;
149            }
150
151            mCurrentDream.mBound = true;
152            mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT);
153        } finally {
154            Trace.traceEnd(Trace.TRACE_TAG_POWER);
155        }
156    }
157
158    public void stopDream(boolean immediate) {
159        if (mCurrentDream == null) {
160            return;
161        }
162
163        Trace.traceBegin(Trace.TRACE_TAG_POWER, "stopDream");
164        try {
165            if (!immediate) {
166                if (mCurrentDream.mWakingGently) {
167                    return; // already waking gently
168                }
169
170                if (mCurrentDream.mService != null) {
171                    // Give the dream a moment to wake up and finish itself gently.
172                    mCurrentDream.mWakingGently = true;
173                    try {
174                        mCurrentDream.mService.wakeUp();
175                        mHandler.postDelayed(mStopStubbornDreamRunnable, DREAM_FINISH_TIMEOUT);
176                        return;
177                    } catch (RemoteException ex) {
178                        // oh well, we tried, finish immediately instead
179                    }
180                }
181            }
182
183            final DreamRecord oldDream = mCurrentDream;
184            mCurrentDream = null;
185            Slog.i(TAG, "Stopping dream: name=" + oldDream.mName
186                    + ", isTest=" + oldDream.mIsTest + ", canDoze=" + oldDream.mCanDoze
187                    + ", userId=" + oldDream.mUserId);
188
189            mHandler.removeCallbacks(mStopUnconnectedDreamRunnable);
190            mHandler.removeCallbacks(mStopStubbornDreamRunnable);
191
192            if (oldDream.mSentStartBroadcast) {
193                mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL);
194            }
195
196            if (oldDream.mService != null) {
197                // Tell the dream that it's being stopped so that
198                // it can shut down nicely before we yank its window token out from
199                // under it.
200                try {
201                    oldDream.mService.detach();
202                } catch (RemoteException ex) {
203                    // we don't care; this thing is on the way out
204                }
205
206                try {
207                    oldDream.mService.asBinder().unlinkToDeath(oldDream, 0);
208                } catch (NoSuchElementException ex) {
209                    // don't care
210                }
211                oldDream.mService = null;
212            }
213
214            if (oldDream.mBound) {
215                mContext.unbindService(oldDream);
216            }
217
218            try {
219                mIWindowManager.removeWindowToken(oldDream.mToken);
220            } catch (RemoteException ex) {
221                Slog.w(TAG, "Error removing window token for dream.", ex);
222            }
223
224            mHandler.post(new Runnable() {
225                @Override
226                public void run() {
227                    mListener.onDreamStopped(oldDream.mToken);
228                }
229            });
230        } finally {
231            Trace.traceEnd(Trace.TRACE_TAG_POWER);
232        }
233    }
234
235    private void attach(IDreamService service) {
236        try {
237            service.asBinder().linkToDeath(mCurrentDream, 0);
238            service.attach(mCurrentDream.mToken, mCurrentDream.mCanDoze);
239        } catch (RemoteException ex) {
240            Slog.e(TAG, "The dream service died unexpectedly.", ex);
241            stopDream(true /*immediate*/);
242            return;
243        }
244
245        mCurrentDream.mService = service;
246
247        if (!mCurrentDream.mIsTest) {
248            mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL);
249            mCurrentDream.mSentStartBroadcast = true;
250        }
251    }
252
253    /**
254     * Callback interface to be implemented by the {@link DreamManagerService}.
255     */
256    public interface Listener {
257        void onDreamStopped(Binder token);
258    }
259
260    private final class DreamRecord implements DeathRecipient, ServiceConnection {
261        public final Binder mToken;
262        public final ComponentName mName;
263        public final boolean mIsTest;
264        public final boolean mCanDoze;
265        public final int mUserId;
266
267        public boolean mBound;
268        public boolean mConnected;
269        public IDreamService mService;
270        public boolean mSentStartBroadcast;
271
272        public boolean mWakingGently;
273
274        public DreamRecord(Binder token, ComponentName name,
275                boolean isTest, boolean canDoze, int userId) {
276            mToken = token;
277            mName = name;
278            mIsTest = isTest;
279            mCanDoze = canDoze;
280            mUserId  = userId;
281        }
282
283        // May be called on any thread.
284        @Override
285        public void binderDied() {
286            mHandler.post(new Runnable() {
287                @Override
288                public void run() {
289                    mService = null;
290                    if (mCurrentDream == DreamRecord.this) {
291                        stopDream(true /*immediate*/);
292                    }
293                }
294            });
295        }
296
297        // May be called on any thread.
298        @Override
299        public void onServiceConnected(ComponentName name, final IBinder service) {
300            mHandler.post(new Runnable() {
301                @Override
302                public void run() {
303                    mConnected = true;
304                    if (mCurrentDream == DreamRecord.this && mService == null) {
305                        attach(IDreamService.Stub.asInterface(service));
306                    }
307                }
308            });
309        }
310
311        // May be called on any thread.
312        @Override
313        public void onServiceDisconnected(ComponentName name) {
314            mHandler.post(new Runnable() {
315                @Override
316                public void run() {
317                    mService = null;
318                    if (mCurrentDream == DreamRecord.this) {
319                        stopDream(true /*immediate*/);
320                    }
321                }
322            });
323        }
324    }
325}