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