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