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