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