DreamController.java revision 2687550272ba061448f5d5b914700dc335299ee7
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    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
74    public DreamController(Context context, Handler handler, Listener listener) {
75        mContext = context;
76        mHandler = handler;
77        mListener = listener;
78        mIWindowManager = WindowManagerGlobal.getWindowManagerService();
79    }
80
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("    mCanDoze=" + mCurrentDream.mCanDoze);
89            pw.println("    mUserId=" + mCurrentDream.mUserId);
90            pw.println("    mBound=" + mCurrentDream.mBound);
91            pw.println("    mService=" + mCurrentDream.mService);
92            pw.println("    mSentStartBroadcast=" + mCurrentDream.mSentStartBroadcast);
93        } else {
94            pw.println("  mCurrentDream: null");
95        }
96    }
97
98    public void startDream(Binder token, ComponentName name,
99            boolean isTest, boolean canDoze, int userId) {
100        stopDream();
101
102        // Close the notification shade. Don't need to send to all, but better to be explicit.
103        mContext.sendBroadcastAsUser(mCloseNotificationShadeIntent, UserHandle.ALL);
104
105        Slog.i(TAG, "Starting dream: name=" + name
106                + ", isTest=" + isTest + ", canDoze=" + canDoze
107                + ", userId=" + userId);
108
109        mCurrentDream = new DreamRecord(token, name, isTest, canDoze, userId);
110
111        try {
112            mIWindowManager.addWindowToken(token, WindowManager.LayoutParams.TYPE_DREAM);
113        } catch (RemoteException ex) {
114            Slog.e(TAG, "Unable to add window token for dream.", ex);
115            stopDream();
116            return;
117        }
118
119        Intent intent = new Intent(DreamService.SERVICE_INTERFACE);
120        intent.setComponent(name);
121        intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
122        try {
123            if (!mContext.bindServiceAsUser(intent, mCurrentDream,
124                    Context.BIND_AUTO_CREATE, new UserHandle(userId))) {
125                Slog.e(TAG, "Unable to bind dream service: " + intent);
126                stopDream();
127                return;
128            }
129        } catch (SecurityException ex) {
130            Slog.e(TAG, "Unable to bind dream service: " + intent, ex);
131            stopDream();
132            return;
133        }
134
135        mCurrentDream.mBound = true;
136        mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT);
137    }
138
139    public void stopDream() {
140        if (mCurrentDream == null) {
141            return;
142        }
143
144        final DreamRecord oldDream = mCurrentDream;
145        mCurrentDream = null;
146        Slog.i(TAG, "Stopping dream: name=" + oldDream.mName
147                + ", isTest=" + oldDream.mIsTest + ", canDoze=" + oldDream.mCanDoze
148                + ", userId=" + oldDream.mUserId);
149
150        mHandler.removeCallbacks(mStopUnconnectedDreamRunnable);
151
152        if (oldDream.mSentStartBroadcast) {
153            mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL);
154        }
155
156        if (oldDream.mService != null) {
157            // Tell the dream that it's being stopped so that
158            // it can shut down nicely before we yank its window token out from
159            // under it.
160            try {
161                oldDream.mService.detach();
162            } catch (RemoteException ex) {
163                // we don't care; this thing is on the way out
164            }
165
166            try {
167                oldDream.mService.asBinder().unlinkToDeath(oldDream, 0);
168            } catch (NoSuchElementException ex) {
169                // don't care
170            }
171            oldDream.mService = null;
172        }
173
174        if (oldDream.mBound) {
175            mContext.unbindService(oldDream);
176        }
177
178        try {
179            mIWindowManager.removeWindowToken(oldDream.mToken);
180        } catch (RemoteException ex) {
181            Slog.w(TAG, "Error removing window token for dream.", ex);
182        }
183
184        mHandler.post(new Runnable() {
185            @Override
186            public void run() {
187                mListener.onDreamStopped(oldDream.mToken);
188            }
189        });
190    }
191
192    private void attach(IDreamService service) {
193        try {
194            service.asBinder().linkToDeath(mCurrentDream, 0);
195            service.attach(mCurrentDream.mToken, mCurrentDream.mCanDoze);
196        } catch (RemoteException ex) {
197            Slog.e(TAG, "The dream service died unexpectedly.", ex);
198            stopDream();
199            return;
200        }
201
202        mCurrentDream.mService = service;
203
204        if (!mCurrentDream.mIsTest) {
205            mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL);
206            mCurrentDream.mSentStartBroadcast = true;
207        }
208    }
209
210    /**
211     * Callback interface to be implemented by the {@link DreamManagerService}.
212     */
213    public interface Listener {
214        void onDreamStopped(Binder token);
215    }
216
217    private final class DreamRecord implements DeathRecipient, ServiceConnection {
218        public final Binder mToken;
219        public final ComponentName mName;
220        public final boolean mIsTest;
221        public final boolean mCanDoze;
222        public final int mUserId;
223
224        public boolean mBound;
225        public boolean mConnected;
226        public IDreamService mService;
227        public boolean mSentStartBroadcast;
228
229        public DreamRecord(Binder token, ComponentName name,
230                boolean isTest, boolean canDoze, int userId) {
231            mToken = token;
232            mName = name;
233            mIsTest = isTest;
234            mCanDoze = canDoze;
235            mUserId  = userId;
236        }
237
238        // May be called on any thread.
239        @Override
240        public void binderDied() {
241            mHandler.post(new Runnable() {
242                @Override
243                public void run() {
244                    mService = null;
245                    if (mCurrentDream == DreamRecord.this) {
246                        stopDream();
247                    }
248                }
249            });
250        }
251
252        // May be called on any thread.
253        @Override
254        public void onServiceConnected(ComponentName name, final IBinder service) {
255            mHandler.post(new Runnable() {
256                @Override
257                public void run() {
258                    mConnected = true;
259                    if (mCurrentDream == DreamRecord.this && mService == null) {
260                        attach(IDreamService.Stub.asInterface(service));
261                    }
262                }
263            });
264        }
265
266        // May be called on any thread.
267        @Override
268        public void onServiceDisconnected(ComponentName name) {
269            mHandler.post(new Runnable() {
270                @Override
271                public void run() {
272                    mService = null;
273                    if (mCurrentDream == DreamRecord.this) {
274                        stopDream();
275                    }
276                }
277            });
278        }
279    }
280}