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 com.android.internal.logging.MetricsLogger;
20import com.android.internal.logging.MetricsProto.MetricsEvent;
21
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.ServiceConnection;
26import android.os.Binder;
27import android.os.Bundle;
28import android.os.Handler;
29import android.os.IBinder;
30import android.os.IRemoteCallback;
31import android.os.PowerManager;
32import android.os.RemoteException;
33import android.os.IBinder.DeathRecipient;
34import android.os.SystemClock;
35import android.os.Trace;
36import android.os.UserHandle;
37import android.service.dreams.DreamService;
38import android.service.dreams.IDreamService;
39import android.util.Slog;
40import android.view.IWindowManager;
41import android.view.WindowManager;
42import android.view.WindowManagerGlobal;
43
44import java.io.PrintWriter;
45import java.util.NoSuchElementException;
46
47/**
48 * Internal controller for starting and stopping the current dream and managing related state.
49 *
50 * Assumes all operations are called from the dream handler thread.
51 */
52final class DreamController {
53    private static final String TAG = "DreamController";
54
55    // How long we wait for a newly bound dream to create the service connection
56    private static final int DREAM_CONNECTION_TIMEOUT = 5 * 1000;
57
58    // Time to allow the dream to perform an exit transition when waking up.
59    private static final int DREAM_FINISH_TIMEOUT = 5 * 1000;
60
61    private final Context mContext;
62    private final Handler mHandler;
63    private final Listener mListener;
64    private final IWindowManager mIWindowManager;
65    private long mDreamStartTime;
66
67    private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED)
68            .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
69    private final Intent mDreamingStoppedIntent = new Intent(Intent.ACTION_DREAMING_STOPPED)
70            .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
71
72    private final Intent mCloseNotificationShadeIntent;
73
74    private DreamRecord mCurrentDream;
75
76    private final Runnable mStopUnconnectedDreamRunnable = new Runnable() {
77        @Override
78        public void run() {
79            if (mCurrentDream != null && mCurrentDream.mBound && !mCurrentDream.mConnected) {
80                Slog.w(TAG, "Bound dream did not connect in the time allotted");
81                stopDream(true /*immediate*/);
82            }
83        }
84    };
85
86    private final Runnable mStopStubbornDreamRunnable = new Runnable() {
87        @Override
88        public void run() {
89            Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted");
90            stopDream(true /*immediate*/);
91        }
92    };
93
94    public DreamController(Context context, Handler handler, Listener listener) {
95        mContext = context;
96        mHandler = handler;
97        mListener = listener;
98        mIWindowManager = WindowManagerGlobal.getWindowManagerService();
99        mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
100        mCloseNotificationShadeIntent.putExtra("reason", "dream");
101    }
102
103    public void dump(PrintWriter pw) {
104        pw.println("Dreamland:");
105        if (mCurrentDream != null) {
106            pw.println("  mCurrentDream:");
107            pw.println("    mToken=" + mCurrentDream.mToken);
108            pw.println("    mName=" + mCurrentDream.mName);
109            pw.println("    mIsTest=" + mCurrentDream.mIsTest);
110            pw.println("    mCanDoze=" + mCurrentDream.mCanDoze);
111            pw.println("    mUserId=" + mCurrentDream.mUserId);
112            pw.println("    mBound=" + mCurrentDream.mBound);
113            pw.println("    mService=" + mCurrentDream.mService);
114            pw.println("    mSentStartBroadcast=" + mCurrentDream.mSentStartBroadcast);
115            pw.println("    mWakingGently=" + mCurrentDream.mWakingGently);
116        } else {
117            pw.println("  mCurrentDream: null");
118        }
119    }
120
121    public void startDream(Binder token, ComponentName name,
122            boolean isTest, boolean canDoze, int userId, PowerManager.WakeLock wakeLock) {
123        stopDream(true /*immediate*/);
124
125        Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream");
126        try {
127            // Close the notification shade. No need to send to all, but better to be explicit.
128            mContext.sendBroadcastAsUser(mCloseNotificationShadeIntent, UserHandle.ALL);
129
130            Slog.i(TAG, "Starting dream: name=" + name
131                    + ", isTest=" + isTest + ", canDoze=" + canDoze
132                    + ", userId=" + userId);
133
134            mCurrentDream = new DreamRecord(token, name, isTest, canDoze, userId, wakeLock);
135
136            mDreamStartTime = SystemClock.elapsedRealtime();
137            MetricsLogger.visible(mContext,
138                    mCurrentDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING);
139
140            try {
141                mIWindowManager.addWindowToken(token, WindowManager.LayoutParams.TYPE_DREAM);
142            } catch (RemoteException ex) {
143                Slog.e(TAG, "Unable to add window token for dream.", ex);
144                stopDream(true /*immediate*/);
145                return;
146            }
147
148            Intent intent = new Intent(DreamService.SERVICE_INTERFACE);
149            intent.setComponent(name);
150            intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
151            try {
152                if (!mContext.bindServiceAsUser(intent, mCurrentDream,
153                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
154                        new UserHandle(userId))) {
155                    Slog.e(TAG, "Unable to bind dream service: " + intent);
156                    stopDream(true /*immediate*/);
157                    return;
158                }
159            } catch (SecurityException ex) {
160                Slog.e(TAG, "Unable to bind dream service: " + intent, ex);
161                stopDream(true /*immediate*/);
162                return;
163            }
164
165            mCurrentDream.mBound = true;
166            mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT);
167        } finally {
168            Trace.traceEnd(Trace.TRACE_TAG_POWER);
169        }
170    }
171
172    public void stopDream(boolean immediate) {
173        if (mCurrentDream == null) {
174            return;
175        }
176
177        Trace.traceBegin(Trace.TRACE_TAG_POWER, "stopDream");
178        try {
179            if (!immediate) {
180                if (mCurrentDream.mWakingGently) {
181                    return; // already waking gently
182                }
183
184                if (mCurrentDream.mService != null) {
185                    // Give the dream a moment to wake up and finish itself gently.
186                    mCurrentDream.mWakingGently = true;
187                    try {
188                        mCurrentDream.mService.wakeUp();
189                        mHandler.postDelayed(mStopStubbornDreamRunnable, DREAM_FINISH_TIMEOUT);
190                        return;
191                    } catch (RemoteException ex) {
192                        // oh well, we tried, finish immediately instead
193                    }
194                }
195            }
196
197            final DreamRecord oldDream = mCurrentDream;
198            mCurrentDream = null;
199            Slog.i(TAG, "Stopping dream: name=" + oldDream.mName
200                    + ", isTest=" + oldDream.mIsTest + ", canDoze=" + oldDream.mCanDoze
201                    + ", userId=" + oldDream.mUserId);
202            MetricsLogger.hidden(mContext,
203                    oldDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING);
204            MetricsLogger.histogram(mContext,
205                    oldDream.mCanDoze ? "dozing_minutes" : "dreaming_minutes" ,
206                    (int) ((SystemClock.elapsedRealtime() - mDreamStartTime) / (1000L * 60L)));
207
208            mHandler.removeCallbacks(mStopUnconnectedDreamRunnable);
209            mHandler.removeCallbacks(mStopStubbornDreamRunnable);
210
211            if (oldDream.mSentStartBroadcast) {
212                mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL);
213            }
214
215            if (oldDream.mService != null) {
216                // Tell the dream that it's being stopped so that
217                // it can shut down nicely before we yank its window token out from
218                // under it.
219                try {
220                    oldDream.mService.detach();
221                } catch (RemoteException ex) {
222                    // we don't care; this thing is on the way out
223                }
224
225                try {
226                    oldDream.mService.asBinder().unlinkToDeath(oldDream, 0);
227                } catch (NoSuchElementException ex) {
228                    // don't care
229                }
230                oldDream.mService = null;
231            }
232
233            if (oldDream.mBound) {
234                mContext.unbindService(oldDream);
235            }
236            oldDream.releaseWakeLockIfNeeded();
237
238            try {
239                mIWindowManager.removeWindowToken(oldDream.mToken);
240            } catch (RemoteException ex) {
241                Slog.w(TAG, "Error removing window token for dream.", ex);
242            }
243
244            mHandler.post(new Runnable() {
245                @Override
246                public void run() {
247                    mListener.onDreamStopped(oldDream.mToken);
248                }
249            });
250        } finally {
251            Trace.traceEnd(Trace.TRACE_TAG_POWER);
252        }
253    }
254
255    private void attach(IDreamService service) {
256        try {
257            service.asBinder().linkToDeath(mCurrentDream, 0);
258            service.attach(mCurrentDream.mToken, mCurrentDream.mCanDoze,
259                    mCurrentDream.mDreamingStartedCallback);
260        } catch (RemoteException ex) {
261            Slog.e(TAG, "The dream service died unexpectedly.", ex);
262            stopDream(true /*immediate*/);
263            return;
264        }
265
266        mCurrentDream.mService = service;
267
268        if (!mCurrentDream.mIsTest) {
269            mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL);
270            mCurrentDream.mSentStartBroadcast = true;
271        }
272    }
273
274    /**
275     * Callback interface to be implemented by the {@link DreamManagerService}.
276     */
277    public interface Listener {
278        void onDreamStopped(Binder token);
279    }
280
281    private final class DreamRecord implements DeathRecipient, ServiceConnection {
282        public final Binder mToken;
283        public final ComponentName mName;
284        public final boolean mIsTest;
285        public final boolean mCanDoze;
286        public final int mUserId;
287
288        public PowerManager.WakeLock mWakeLock;
289        public boolean mBound;
290        public boolean mConnected;
291        public IDreamService mService;
292        public boolean mSentStartBroadcast;
293
294        public boolean mWakingGently;
295
296        public DreamRecord(Binder token, ComponentName name,
297                boolean isTest, boolean canDoze, int userId, PowerManager.WakeLock wakeLock) {
298            mToken = token;
299            mName = name;
300            mIsTest = isTest;
301            mCanDoze = canDoze;
302            mUserId  = userId;
303            mWakeLock = wakeLock;
304            // Hold the lock while we're waiting for the service to connect and start dreaming.
305            // Released after the service has started dreaming, we stop dreaming, or it timed out.
306            mWakeLock.acquire();
307            mHandler.postDelayed(mReleaseWakeLockIfNeeded, 10000);
308        }
309
310        // May be called on any thread.
311        @Override
312        public void binderDied() {
313            mHandler.post(new Runnable() {
314                @Override
315                public void run() {
316                    mService = null;
317                    if (mCurrentDream == DreamRecord.this) {
318                        stopDream(true /*immediate*/);
319                    }
320                }
321            });
322        }
323
324        // May be called on any thread.
325        @Override
326        public void onServiceConnected(ComponentName name, final IBinder service) {
327            mHandler.post(new Runnable() {
328                @Override
329                public void run() {
330                    mConnected = true;
331                    if (mCurrentDream == DreamRecord.this && mService == null) {
332                        attach(IDreamService.Stub.asInterface(service));
333                        // Wake lock will be released once dreaming starts.
334                    } else {
335                        releaseWakeLockIfNeeded();
336                    }
337                }
338            });
339        }
340
341        // May be called on any thread.
342        @Override
343        public void onServiceDisconnected(ComponentName name) {
344            mHandler.post(new Runnable() {
345                @Override
346                public void run() {
347                    mService = null;
348                    if (mCurrentDream == DreamRecord.this) {
349                        stopDream(true /*immediate*/);
350                    }
351                }
352            });
353        }
354
355        void releaseWakeLockIfNeeded() {
356            if (mWakeLock != null) {
357                mWakeLock.release();
358                mWakeLock = null;
359                mHandler.removeCallbacks(mReleaseWakeLockIfNeeded);
360            }
361        }
362
363        final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded;
364
365        final IRemoteCallback mDreamingStartedCallback = new IRemoteCallback.Stub() {
366            // May be called on any thread.
367            @Override
368            public void sendResult(Bundle data) throws RemoteException {
369                mHandler.post(mReleaseWakeLockIfNeeded);
370            }
371        };
372    }
373}