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