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 android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.ServiceConnection; 23 import android.os.Binder; 24 import android.os.Handler; 25 import android.os.IBinder; 26 import android.os.RemoteException; 27 import android.os.IBinder.DeathRecipient; 28 import android.os.UserHandle; 29 import android.service.dreams.DreamService; 30 import android.service.dreams.IDreamService; 31 import android.util.Slog; 32 import android.view.IWindowManager; 33 import android.view.WindowManager; 34 import android.view.WindowManagerGlobal; 35 36 import java.io.PrintWriter; 37 import 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 */ 44 final 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(" mUserId=" + mCurrentDream.mUserId); 89 pw.println(" mBound=" + mCurrentDream.mBound); 90 pw.println(" mService=" + mCurrentDream.mService); 91 pw.println(" mSentStartBroadcast=" + mCurrentDream.mSentStartBroadcast); 92 } else { 93 pw.println(" mCurrentDream: null"); 94 } 95 } 96 97 public void startDream(Binder token, ComponentName name, boolean isTest, int userId) { 98 stopDream(); 99 100 // Close the notification shade. Don't need to send to all, but better to be explicit. 101 mContext.sendBroadcastAsUser(mCloseNotificationShadeIntent, UserHandle.ALL); 102 103 Slog.i(TAG, "Starting dream: name=" + name + ", isTest=" + isTest + ", userId=" + userId); 104 105 mCurrentDream = new DreamRecord(token, name, isTest, userId); 106 107 try { 108 mIWindowManager.addWindowToken(token, WindowManager.LayoutParams.TYPE_DREAM); 109 } catch (RemoteException ex) { 110 Slog.e(TAG, "Unable to add window token for dream.", ex); 111 stopDream(); 112 return; 113 } 114 115 Intent intent = new Intent(DreamService.SERVICE_INTERFACE); 116 intent.setComponent(name); 117 intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 118 try { 119 if (!mContext.bindServiceAsUser(intent, mCurrentDream, 120 Context.BIND_AUTO_CREATE, new UserHandle(userId))) { 121 Slog.e(TAG, "Unable to bind dream service: " + intent); 122 stopDream(); 123 return; 124 } 125 } catch (SecurityException ex) { 126 Slog.e(TAG, "Unable to bind dream service: " + intent, ex); 127 stopDream(); 128 return; 129 } 130 131 mCurrentDream.mBound = true; 132 mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT); 133 } 134 135 public void stopDream() { 136 if (mCurrentDream == null) { 137 return; 138 } 139 140 final DreamRecord oldDream = mCurrentDream; 141 mCurrentDream = null; 142 Slog.i(TAG, "Stopping dream: name=" + oldDream.mName 143 + ", isTest=" + oldDream.mIsTest + ", userId=" + oldDream.mUserId); 144 145 mHandler.removeCallbacks(mStopUnconnectedDreamRunnable); 146 147 if (oldDream.mSentStartBroadcast) { 148 mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL); 149 } 150 151 if (oldDream.mService != null) { 152 // Tell the dream that it's being stopped so that 153 // it can shut down nicely before we yank its window token out from 154 // under it. 155 try { 156 oldDream.mService.detach(); 157 } catch (RemoteException ex) { 158 // we don't care; this thing is on the way out 159 } 160 161 try { 162 oldDream.mService.asBinder().unlinkToDeath(oldDream, 0); 163 } catch (NoSuchElementException ex) { 164 // don't care 165 } 166 oldDream.mService = null; 167 } 168 169 if (oldDream.mBound) { 170 mContext.unbindService(oldDream); 171 } 172 173 try { 174 mIWindowManager.removeWindowToken(oldDream.mToken); 175 } catch (RemoteException ex) { 176 Slog.w(TAG, "Error removing window token for dream.", ex); 177 } 178 179 mHandler.post(new Runnable() { 180 @Override 181 public void run() { 182 mListener.onDreamStopped(oldDream.mToken); 183 } 184 }); 185 } 186 187 private void attach(IDreamService service) { 188 try { 189 service.asBinder().linkToDeath(mCurrentDream, 0); 190 service.attach(mCurrentDream.mToken); 191 } catch (RemoteException ex) { 192 Slog.e(TAG, "The dream service died unexpectedly.", ex); 193 stopDream(); 194 return; 195 } 196 197 mCurrentDream.mService = service; 198 199 if (!mCurrentDream.mIsTest) { 200 mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL); 201 mCurrentDream.mSentStartBroadcast = true; 202 } 203 } 204 205 /** 206 * Callback interface to be implemented by the {@link DreamManagerService}. 207 */ 208 public interface Listener { 209 void onDreamStopped(Binder token); 210 } 211 212 private final class DreamRecord implements DeathRecipient, ServiceConnection { 213 public final Binder mToken; 214 public final ComponentName mName; 215 public final boolean mIsTest; 216 public final int mUserId; 217 218 public boolean mBound; 219 public boolean mConnected; 220 public IDreamService mService; 221 public boolean mSentStartBroadcast; 222 223 public DreamRecord(Binder token, ComponentName name, 224 boolean isTest, int userId) { 225 mToken = token; 226 mName = name; 227 mIsTest = isTest; 228 mUserId = userId; 229 } 230 231 // May be called on any thread. 232 @Override 233 public void binderDied() { 234 mHandler.post(new Runnable() { 235 @Override 236 public void run() { 237 mService = null; 238 if (mCurrentDream == DreamRecord.this) { 239 stopDream(); 240 } 241 } 242 }); 243 } 244 245 // May be called on any thread. 246 @Override 247 public void onServiceConnected(ComponentName name, final IBinder service) { 248 mHandler.post(new Runnable() { 249 @Override 250 public void run() { 251 mConnected = true; 252 if (mCurrentDream == DreamRecord.this && mService == null) { 253 attach(IDreamService.Stub.asInterface(service)); 254 } 255 } 256 }); 257 } 258 259 // May be called on any thread. 260 @Override 261 public void onServiceDisconnected(ComponentName name) { 262 mHandler.post(new Runnable() { 263 @Override 264 public void run() { 265 mService = null; 266 if (mCurrentDream == DreamRecord.this) { 267 stopDream(); 268 } 269 } 270 }); 271 } 272 } 273 }