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}