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