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