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