TileLifecycleManager.java revision c0d7058b14c24cd07912f5629c26b39b7b4673d5
1/* 2 * Copyright (C) 2015 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 */ 16package com.android.systemui.qs.external; 17 18import libcore.util.Objects; 19 20import android.app.AppGlobals; 21import android.content.BroadcastReceiver; 22import android.content.ComponentName; 23import android.content.Context; 24import android.content.Intent; 25import android.content.IntentFilter; 26import android.content.ServiceConnection; 27import android.content.pm.PackageManager; 28import android.content.pm.ServiceInfo; 29import android.net.Uri; 30import android.os.Handler; 31import android.os.IBinder; 32import android.os.RemoteException; 33import android.os.UserHandle; 34import android.service.quicksettings.IQSService; 35import android.service.quicksettings.IQSTileService; 36import android.service.quicksettings.Tile; 37import android.support.annotation.VisibleForTesting; 38import android.util.ArraySet; 39import android.util.Log; 40 41import java.util.Set; 42 43/** 44 * Manages the lifecycle of a TileService. 45 * <p> 46 * Will keep track of all calls on the IQSTileService interface and will relay those calls to the 47 * TileService as soon as it is bound. It will only bind to the service when it is allowed to 48 * ({@link #setBindService(boolean)}) and when the service is available. 49 */ 50public class TileLifecycleManager extends BroadcastReceiver implements 51 IQSTileService, ServiceConnection, IBinder.DeathRecipient { 52 public static final boolean DEBUG = false; 53 54 private static final String TAG = "TileLifecycleManager"; 55 56 private static final int MSG_ON_ADDED = 0; 57 private static final int MSG_ON_REMOVED = 1; 58 private static final int MSG_ON_CLICK = 2; 59 private static final int MSG_ON_UNLOCK_COMPLETE = 3; 60 61 // Bind retry control. 62 private static final int MAX_BIND_RETRIES = 5; 63 private static final int BIND_RETRY_DELAY = 1000; 64 65 private final Context mContext; 66 private final Handler mHandler; 67 private final Intent mIntent; 68 private final UserHandle mUser; 69 70 private Set<Integer> mQueuedMessages = new ArraySet<>(); 71 private QSTileServiceWrapper mWrapper; 72 private boolean mListening; 73 private Tile mTile; 74 private IBinder mClickBinder; 75 76 private int mBindTryCount; 77 private boolean mBound; 78 @VisibleForTesting 79 boolean mReceiverRegistered; 80 private IQSService mService; 81 private boolean mUnbindImmediate; 82 83 public TileLifecycleManager(Handler handler, Context context, Intent intent, UserHandle user) { 84 mContext = context; 85 mHandler = handler; 86 mIntent = intent; 87 mUser = user; 88 } 89 90 public ComponentName getComponent() { 91 return mIntent.getComponent(); 92 } 93 94 public boolean hasPendingClick() { 95 synchronized (mQueuedMessages) { 96 return mQueuedMessages.contains(MSG_ON_CLICK); 97 } 98 } 99 100 /** 101 * Binds just long enough to send any queued messages, then unbinds. 102 */ 103 public void flushMessagesAndUnbind() { 104 mUnbindImmediate = true; 105 setBindService(true); 106 } 107 108 public void setBindService(boolean bind) { 109 mBound = bind; 110 if (bind) { 111 if (mBindTryCount == MAX_BIND_RETRIES) { 112 // Too many failures, give up on this tile until an update. 113 startPackageListening(); 114 return; 115 } 116 if (!checkComponentState()) { 117 return; 118 } 119 if (DEBUG) Log.d(TAG, "Binding service " + mIntent); 120 mBindTryCount++; 121 mContext.bindServiceAsUser(mIntent, this, 122 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, 123 mUser); 124 } else { 125 if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent); 126 // Give it another chance next time it needs to be bound, out of kindness. 127 mBindTryCount = 0; 128 mWrapper = null; 129 mContext.unbindService(this); 130 } 131 } 132 133 @Override 134 public void onServiceConnected(ComponentName name, IBinder service) { 135 if (DEBUG) Log.d(TAG, "onServiceConnected " + name); 136 // Got a connection, set the binding count to 0. 137 mBindTryCount = 0; 138 mWrapper = new QSTileServiceWrapper(Stub.asInterface(service)); 139 try { 140 service.linkToDeath(this, 0); 141 } catch (RemoteException e) { 142 } 143 setQSService(mService); 144 setQSTile(mTile); 145 handlePendingMessages(); 146 } 147 148 @Override 149 public void onServiceDisconnected(ComponentName name) { 150 if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name); 151 mWrapper = null; 152 } 153 154 private void handlePendingMessages() { 155 // This ordering is laid out manually to make sure we preserve the TileService 156 // lifecycle. 157 ArraySet<Integer> queue; 158 synchronized (mQueuedMessages) { 159 queue = new ArraySet<>(mQueuedMessages); 160 mQueuedMessages.clear(); 161 } 162 if (queue.contains(MSG_ON_ADDED)) { 163 if (DEBUG) Log.d(TAG, "Handling pending onAdded"); 164 onTileAdded(); 165 } 166 if (mListening) { 167 if (DEBUG) Log.d(TAG, "Handling pending onStartListening"); 168 onStartListening(); 169 } 170 if (queue.contains(MSG_ON_CLICK)) { 171 if (DEBUG) Log.d(TAG, "Handling pending onClick"); 172 if (!mListening) { 173 Log.w(TAG, "Managed to get click on non-listening state..."); 174 // Skipping click since lost click privileges. 175 } else { 176 onClick(mClickBinder); 177 } 178 } 179 if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) { 180 if (DEBUG) Log.d(TAG, "Handling pending onUnlockComplete"); 181 if (!mListening) { 182 Log.w(TAG, "Managed to get unlock on non-listening state..."); 183 // Skipping unlock since lost click privileges. 184 } else { 185 onUnlockComplete(); 186 } 187 } 188 if (queue.contains(MSG_ON_REMOVED)) { 189 if (DEBUG) Log.d(TAG, "Handling pending onRemoved"); 190 if (mListening) { 191 Log.w(TAG, "Managed to get remove in listening state..."); 192 onStopListening(); 193 } 194 onTileRemoved(); 195 } 196 if (mUnbindImmediate) { 197 mUnbindImmediate = false; 198 setBindService(false); 199 } 200 } 201 202 public void handleDestroy() { 203 if (DEBUG) Log.d(TAG, "handleDestroy"); 204 if (mReceiverRegistered) { 205 stopPackageListening(); 206 } 207 } 208 209 private void handleDeath() { 210 if (mWrapper == null) return; 211 mWrapper = null; 212 if (!mBound) return; 213 if (DEBUG) Log.d(TAG, "handleDeath"); 214 if (checkComponentState()) { 215 mHandler.postDelayed(new Runnable() { 216 @Override 217 public void run() { 218 if (mBound) { 219 // Retry binding. 220 setBindService(true); 221 } 222 } 223 }, BIND_RETRY_DELAY); 224 } 225 } 226 227 @Override 228 public void setQSTile(Tile tile) { 229 if (DEBUG) Log.d(TAG, "setQSTile " + tile); 230 mTile = tile; 231 if (mWrapper != null && !mWrapper.setQSTile(tile)) { 232 handleDeath(); 233 } 234 } 235 236 private boolean checkComponentState() { 237 PackageManager pm = mContext.getPackageManager(); 238 if (!isPackageAvailable(pm) || !isComponentAvailable(pm)) { 239 startPackageListening(); 240 return false; 241 } 242 return true; 243 } 244 245 private void startPackageListening() { 246 if (DEBUG) Log.d(TAG, "startPackageListening"); 247 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 248 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 249 filter.addDataScheme("package"); 250 mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler); 251 mReceiverRegistered = true; 252 } 253 254 private void stopPackageListening() { 255 if (DEBUG) Log.d(TAG, "stopPackageListening"); 256 mContext.unregisterReceiver(this); 257 mReceiverRegistered = false; 258 } 259 260 @Override 261 public void onReceive(Context context, Intent intent) { 262 if (DEBUG) Log.d(TAG, "onReceive: " + intent); 263 Uri data = intent.getData(); 264 String pkgName = data.getEncodedSchemeSpecificPart(); 265 if (!Objects.equal(pkgName, mIntent.getComponent().getPackageName())) { 266 return; 267 } 268 stopPackageListening(); 269 if (mBound) { 270 // Trying to bind again will check the state of the package before bothering to bind. 271 if (DEBUG) Log.d(TAG, "Trying to rebind"); 272 setBindService(true); 273 } 274 } 275 276 private boolean isComponentAvailable(PackageManager pm) { 277 String packageName = mIntent.getComponent().getPackageName(); 278 try { 279 ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(mIntent.getComponent(), 280 0, mUser.getIdentifier()); 281 if (DEBUG && si == null) Log.d(TAG, "Can't find component " + mIntent.getComponent()); 282 return si != null; 283 } catch (RemoteException e) { 284 // Shouldn't happen. 285 } 286 return false; 287 } 288 289 private boolean isPackageAvailable(PackageManager pm) { 290 String packageName = mIntent.getComponent().getPackageName(); 291 try { 292 pm.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier()); 293 return true; 294 } catch (PackageManager.NameNotFoundException e) { 295 if (DEBUG) Log.d(TAG, "Package not available: " + packageName, e); 296 else Log.d(TAG, "Package not available: " + packageName); 297 } 298 return false; 299 } 300 301 private void queueMessage(int message) { 302 synchronized (mQueuedMessages) { 303 mQueuedMessages.add(message); 304 } 305 } 306 307 @Override 308 public void setQSService(IQSService service) { 309 mService = service; 310 if (mWrapper == null || !mWrapper.setQSService(service)) { 311 handleDeath(); 312 } 313 } 314 315 @Override 316 public void onTileAdded() { 317 if (DEBUG) Log.d(TAG, "onTileAdded"); 318 if (mWrapper == null || !mWrapper.onTileAdded()) { 319 queueMessage(MSG_ON_ADDED); 320 handleDeath(); 321 } 322 } 323 324 @Override 325 public void onTileRemoved() { 326 if (DEBUG) Log.d(TAG, "onTileRemoved"); 327 if (mWrapper == null || !mWrapper.onTileRemoved()) { 328 queueMessage(MSG_ON_REMOVED); 329 handleDeath(); 330 } 331 } 332 333 @Override 334 public void onStartListening() { 335 if (DEBUG) Log.d(TAG, "onStartListening"); 336 mListening = true; 337 if (mWrapper != null && !mWrapper.onStartListening()) { 338 handleDeath(); 339 } 340 } 341 342 @Override 343 public void onStopListening() { 344 if (DEBUG) Log.d(TAG, "onStopListening"); 345 mListening = false; 346 if (mWrapper != null && !mWrapper.onStopListening()) { 347 handleDeath(); 348 } 349 } 350 351 @Override 352 public void onClick(IBinder iBinder) { 353 if (DEBUG) Log.d(TAG, "onClick " + iBinder); 354 if (mWrapper == null || !mWrapper.onClick(iBinder)) { 355 mClickBinder = iBinder; 356 queueMessage(MSG_ON_CLICK); 357 handleDeath(); 358 } 359 } 360 361 @Override 362 public void onUnlockComplete() { 363 if (DEBUG) Log.d(TAG, "onUnlockComplete"); 364 if (mWrapper == null || !mWrapper.onUnlockComplete()) { 365 queueMessage(MSG_ON_UNLOCK_COMPLETE); 366 handleDeath(); 367 } 368 } 369 370 @Override 371 public IBinder asBinder() { 372 return mWrapper != null ? mWrapper.asBinder() : null; 373 } 374 375 @Override 376 public void binderDied() { 377 if (DEBUG) Log.d(TAG, "binderDeath"); 378 handleDeath(); 379 } 380} 381