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    private final Context mContext;
48    private final Handler mHandler;
49    private final Listener mListener;
50    private final IWindowManager mIWindowManager;
51
52    private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED)
53            .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
54    private final Intent mDreamingStoppedIntent = new Intent(Intent.ACTION_DREAMING_STOPPED)
55            .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
56
57    private final Intent mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
58
59    private DreamRecord mCurrentDream;
60
61    public DreamController(Context context, Handler handler, Listener listener) {
62        mContext = context;
63        mHandler = handler;
64        mListener = listener;
65        mIWindowManager = WindowManagerGlobal.getWindowManagerService();
66    }
67
68    public void dump(PrintWriter pw) {
69        pw.println("Dreamland:");
70        if (mCurrentDream != null) {
71            pw.println("  mCurrentDream:");
72            pw.println("    mToken=" + mCurrentDream.mToken);
73            pw.println("    mName=" + mCurrentDream.mName);
74            pw.println("    mIsTest=" + mCurrentDream.mIsTest);
75            pw.println("    mUserId=" + mCurrentDream.mUserId);
76            pw.println("    mBound=" + mCurrentDream.mBound);
77            pw.println("    mService=" + mCurrentDream.mService);
78            pw.println("    mSentStartBroadcast=" + mCurrentDream.mSentStartBroadcast);
79        } else {
80            pw.println("  mCurrentDream: null");
81        }
82    }
83
84    public void startDream(Binder token, ComponentName name, boolean isTest, int userId) {
85        stopDream();
86
87        // Close the notification shade. Don't need to send to all, but better to be explicit.
88        mContext.sendBroadcastAsUser(mCloseNotificationShadeIntent, UserHandle.ALL);
89
90        Slog.i(TAG, "Starting dream: name=" + name + ", isTest=" + isTest + ", userId=" + userId);
91
92        mCurrentDream = new DreamRecord(token, name, isTest, userId);
93
94        try {
95            mIWindowManager.addWindowToken(token, WindowManager.LayoutParams.TYPE_DREAM);
96        } catch (RemoteException ex) {
97            Slog.e(TAG, "Unable to add window token for dream.", ex);
98            stopDream();
99            return;
100        }
101
102        Intent intent = new Intent(DreamService.SERVICE_INTERFACE);
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.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL);
133        }
134
135        if (oldDream.mService != null) {
136            // Tell the dream that it's being stopped so that
137            // it can shut down nicely before we yank its window token out from
138            // under it.
139            try {
140                oldDream.mService.detach();
141            } catch (RemoteException ex) {
142                // we don't care; this thing is on the way out
143            }
144
145            try {
146                oldDream.mService.asBinder().unlinkToDeath(oldDream, 0);
147            } catch (NoSuchElementException ex) {
148                // don't care
149            }
150            oldDream.mService = null;
151        }
152
153        if (oldDream.mBound) {
154            mContext.unbindService(oldDream);
155        }
156
157        try {
158            mIWindowManager.removeWindowToken(oldDream.mToken);
159        } catch (RemoteException ex) {
160            Slog.w(TAG, "Error removing window token for dream.", ex);
161        }
162
163        mHandler.post(new Runnable() {
164            @Override
165            public void run() {
166                mListener.onDreamStopped(oldDream.mToken);
167            }
168        });
169    }
170
171    private void attach(IDreamService service) {
172        try {
173            service.asBinder().linkToDeath(mCurrentDream, 0);
174            service.attach(mCurrentDream.mToken);
175        } catch (RemoteException ex) {
176            Slog.e(TAG, "The dream service died unexpectedly.", ex);
177            stopDream();
178            return;
179        }
180
181        mCurrentDream.mService = service;
182
183        if (!mCurrentDream.mIsTest) {
184            mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL);
185            mCurrentDream.mSentStartBroadcast = true;
186        }
187    }
188
189    /**
190     * Callback interface to be implemented by the {@link DreamManagerService}.
191     */
192    public interface Listener {
193        void onDreamStopped(Binder token);
194    }
195
196    private final class DreamRecord implements DeathRecipient, ServiceConnection {
197        public final Binder mToken;
198        public final ComponentName mName;
199        public final boolean mIsTest;
200        public final int mUserId;
201
202        public boolean mBound;
203        public IDreamService mService;
204        public boolean mSentStartBroadcast;
205
206        public DreamRecord(Binder token, ComponentName name,
207                boolean isTest, int userId) {
208            mToken = token;
209            mName = name;
210            mIsTest = isTest;
211            mUserId  = userId;
212        }
213
214        // May be called on any thread.
215        @Override
216        public void binderDied() {
217            mHandler.post(new Runnable() {
218                @Override
219                public void run() {
220                    mService = null;
221                    if (mCurrentDream == DreamRecord.this) {
222                        stopDream();
223                    }
224                }
225            });
226        }
227
228        // May be called on any thread.
229        @Override
230        public void onServiceConnected(ComponentName name, final IBinder service) {
231            mHandler.post(new Runnable() {
232                @Override
233                public void run() {
234                    if (mCurrentDream == DreamRecord.this && mService == null) {
235                        attach(IDreamService.Stub.asInterface(service));
236                    }
237                }
238            });
239        }
240
241        // May be called on any thread.
242        @Override
243        public void onServiceDisconnected(ComponentName name) {
244            mHandler.post(new Runnable() {
245                @Override
246                public void run() {
247                    mService = null;
248                    if (mCurrentDream == DreamRecord.this) {
249                        stopDream();
250                    }
251                }
252            });
253        }
254    }
255}