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