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 17 package com.android.server.dreams; 18 19 import com.android.internal.logging.MetricsLogger; 20 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 21 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.ServiceConnection; 26 import android.os.Binder; 27 import android.os.Bundle; 28 import android.os.Handler; 29 import android.os.IBinder; 30 import android.os.IRemoteCallback; 31 import android.os.PowerManager; 32 import android.os.RemoteException; 33 import android.os.IBinder.DeathRecipient; 34 import android.os.SystemClock; 35 import android.os.Trace; 36 import android.os.UserHandle; 37 import android.service.dreams.DreamService; 38 import android.service.dreams.IDreamService; 39 import android.util.Slog; 40 import android.view.IWindowManager; 41 import android.view.WindowManager; 42 import android.view.WindowManagerGlobal; 43 44 import java.io.PrintWriter; 45 import java.util.NoSuchElementException; 46 47 import static android.view.Display.DEFAULT_DISPLAY; 48 import 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 */ 55 final 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 }