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