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