TileLifecycleManager.java revision 6d21e0d8d2da2a94e47af8b8f0b93a50972ab480
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 private TileChangeListener mChangeListener; 84 // Return value from bindServiceAsUser, determines whether safe to call unbind. 85 private boolean mIsBound; 86 87 public TileLifecycleManager(Handler handler, Context context, Intent intent, UserHandle user) { 88 mContext = context; 89 mHandler = handler; 90 mIntent = intent; 91 mUser = user; 92 if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser); 93 } 94 95 public ComponentName getComponent() { 96 return mIntent.getComponent(); 97 } 98 99 public boolean hasPendingClick() { 100 synchronized (mQueuedMessages) { 101 return mQueuedMessages.contains(MSG_ON_CLICK); 102 } 103 } 104 105 public boolean isActiveTile() { 106 try { 107 ServiceInfo info = mContext.getPackageManager().getServiceInfo(mIntent.getComponent(), 108 PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA); 109 return info.metaData != null 110 && info.metaData.getBoolean(TileService.META_DATA_ACTIVE_TILE, false); 111 } catch (NameNotFoundException e) { 112 return false; 113 } 114 } 115 116 /** 117 * Binds just long enough to send any queued messages, then unbinds. 118 */ 119 public void flushMessagesAndUnbind() { 120 mUnbindImmediate = true; 121 setBindService(true); 122 } 123 124 public void setBindService(boolean bind) { 125 mBound = bind; 126 if (bind) { 127 if (mBindTryCount == MAX_BIND_RETRIES) { 128 // Too many failures, give up on this tile until an update. 129 startPackageListening(); 130 return; 131 } 132 if (!checkComponentState()) { 133 return; 134 } 135 if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser); 136 mBindTryCount++; 137 mIsBound = mContext.bindServiceAsUser(mIntent, this, 138 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, 139 mUser); 140 } else { 141 if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser); 142 // Give it another chance next time it needs to be bound, out of kindness. 143 mBindTryCount = 0; 144 mWrapper = null; 145 if (mIsBound) { 146 mContext.unbindService(this); 147 mIsBound = false; 148 } 149 } 150 } 151 152 @Override 153 public void onServiceConnected(ComponentName name, IBinder service) { 154 if (DEBUG) Log.d(TAG, "onServiceConnected " + name); 155 // Got a connection, set the binding count to 0. 156 mBindTryCount = 0; 157 final QSTileServiceWrapper wrapper = new QSTileServiceWrapper(Stub.asInterface(service)); 158 try { 159 service.linkToDeath(this, 0); 160 } catch (RemoteException e) { 161 } 162 if (!wrapper.setQSService(mService)) { 163 handleDeath(); 164 return; 165 } 166 if (!wrapper.setQSTile(mTile)) { 167 handleDeath(); 168 return; 169 } 170 mWrapper = wrapper; 171 handlePendingMessages(); 172 } 173 174 @Override 175 public void onServiceDisconnected(ComponentName name) { 176 if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name); 177 handleDeath(); 178 } 179 180 private void handlePendingMessages() { 181 // This ordering is laid out manually to make sure we preserve the TileService 182 // lifecycle. 183 ArraySet<Integer> queue; 184 synchronized (mQueuedMessages) { 185 queue = new ArraySet<>(mQueuedMessages); 186 mQueuedMessages.clear(); 187 } 188 if (queue.contains(MSG_ON_ADDED)) { 189 if (DEBUG) Log.d(TAG, "Handling pending onAdded"); 190 onTileAdded(); 191 } 192 if (mListening) { 193 if (DEBUG) Log.d(TAG, "Handling pending onStartListening"); 194 onStartListening(); 195 } 196 if (queue.contains(MSG_ON_CLICK)) { 197 if (DEBUG) Log.d(TAG, "Handling pending onClick"); 198 if (!mListening) { 199 Log.w(TAG, "Managed to get click on non-listening state..."); 200 // Skipping click since lost click privileges. 201 } else { 202 onClick(mClickBinder); 203 } 204 } 205 if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) { 206 if (DEBUG) Log.d(TAG, "Handling pending onUnlockComplete"); 207 if (!mListening) { 208 Log.w(TAG, "Managed to get unlock on non-listening state..."); 209 // Skipping unlock since lost click privileges. 210 } else { 211 onUnlockComplete(); 212 } 213 } 214 if (queue.contains(MSG_ON_REMOVED)) { 215 if (DEBUG) Log.d(TAG, "Handling pending onRemoved"); 216 if (mListening) { 217 Log.w(TAG, "Managed to get remove in listening state..."); 218 onStopListening(); 219 } 220 onTileRemoved(); 221 } 222 if (mUnbindImmediate) { 223 mUnbindImmediate = false; 224 setBindService(false); 225 } 226 } 227 228 public void handleDestroy() { 229 if (DEBUG) Log.d(TAG, "handleDestroy"); 230 if (mReceiverRegistered) { 231 stopPackageListening(); 232 } 233 } 234 235 private void handleDeath() { 236 if (mWrapper == null) return; 237 mWrapper = null; 238 if (!mBound) return; 239 if (DEBUG) Log.d(TAG, "handleDeath"); 240 if (checkComponentState()) { 241 mHandler.postDelayed(new Runnable() { 242 @Override 243 public void run() { 244 if (mBound) { 245 // Retry binding. 246 setBindService(true); 247 } 248 } 249 }, BIND_RETRY_DELAY); 250 } 251 } 252 253 @Override 254 public void setQSTile(Tile tile) { 255 if (DEBUG) Log.d(TAG, "setQSTile " + tile); 256 mTile = tile; 257 if (mWrapper != null && !mWrapper.setQSTile(tile)) { 258 handleDeath(); 259 } 260 } 261 262 private boolean checkComponentState() { 263 PackageManager pm = mContext.getPackageManager(); 264 if (!isPackageAvailable(pm) || !isComponentAvailable(pm)) { 265 startPackageListening(); 266 return false; 267 } 268 return true; 269 } 270 271 private void startPackageListening() { 272 if (DEBUG) Log.d(TAG, "startPackageListening"); 273 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 274 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 275 filter.addDataScheme("package"); 276 mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler); 277 filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); 278 mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler); 279 mReceiverRegistered = true; 280 } 281 282 private void stopPackageListening() { 283 if (DEBUG) Log.d(TAG, "stopPackageListening"); 284 mContext.unregisterReceiver(this); 285 mReceiverRegistered = false; 286 } 287 288 public void setTileChangeListener(TileChangeListener changeListener) { 289 mChangeListener = changeListener; 290 } 291 292 @Override 293 public void onReceive(Context context, Intent intent) { 294 if (DEBUG) Log.d(TAG, "onReceive: " + intent); 295 if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { 296 Uri data = intent.getData(); 297 String pkgName = data.getEncodedSchemeSpecificPart(); 298 if (!Objects.equal(pkgName, mIntent.getComponent().getPackageName())) { 299 return; 300 } 301 } 302 if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) && mChangeListener != null) { 303 mChangeListener.onTileChanged(mIntent.getComponent()); 304 } 305 stopPackageListening(); 306 if (mBound) { 307 // Trying to bind again will check the state of the package before bothering to bind. 308 if (DEBUG) Log.d(TAG, "Trying to rebind"); 309 setBindService(true); 310 } 311 } 312 313 private boolean isComponentAvailable(PackageManager pm) { 314 String packageName = mIntent.getComponent().getPackageName(); 315 try { 316 ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(mIntent.getComponent(), 317 0, mUser.getIdentifier()); 318 if (DEBUG && si == null) Log.d(TAG, "Can't find component " + mIntent.getComponent()); 319 return si != null; 320 } catch (RemoteException e) { 321 // Shouldn't happen. 322 } 323 return false; 324 } 325 326 private boolean isPackageAvailable(PackageManager pm) { 327 String packageName = mIntent.getComponent().getPackageName(); 328 try { 329 pm.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier()); 330 return true; 331 } catch (PackageManager.NameNotFoundException e) { 332 if (DEBUG) Log.d(TAG, "Package not available: " + packageName, e); 333 else Log.d(TAG, "Package not available: " + packageName); 334 } 335 return false; 336 } 337 338 private void queueMessage(int message) { 339 synchronized (mQueuedMessages) { 340 mQueuedMessages.add(message); 341 } 342 } 343 344 @Override 345 public void setQSService(IQSService service) { 346 mService = service; 347 if (mWrapper == null || !mWrapper.setQSService(service)) { 348 handleDeath(); 349 } 350 } 351 352 @Override 353 public void onTileAdded() { 354 if (DEBUG) Log.d(TAG, "onTileAdded"); 355 if (mWrapper == null || !mWrapper.onTileAdded()) { 356 queueMessage(MSG_ON_ADDED); 357 handleDeath(); 358 } 359 } 360 361 @Override 362 public void onTileRemoved() { 363 if (DEBUG) Log.d(TAG, "onTileRemoved"); 364 if (mWrapper == null || !mWrapper.onTileRemoved()) { 365 queueMessage(MSG_ON_REMOVED); 366 handleDeath(); 367 } 368 } 369 370 @Override 371 public void onStartListening() { 372 if (DEBUG) Log.d(TAG, "onStartListening"); 373 mListening = true; 374 if (mWrapper != null && !mWrapper.onStartListening()) { 375 handleDeath(); 376 } 377 } 378 379 @Override 380 public void onStopListening() { 381 if (DEBUG) Log.d(TAG, "onStopListening"); 382 mListening = false; 383 if (mWrapper != null && !mWrapper.onStopListening()) { 384 handleDeath(); 385 } 386 } 387 388 @Override 389 public void onClick(IBinder iBinder) { 390 if (DEBUG) Log.d(TAG, "onClick " + iBinder + " " + mUser); 391 if (mWrapper == null || !mWrapper.onClick(iBinder)) { 392 mClickBinder = iBinder; 393 queueMessage(MSG_ON_CLICK); 394 handleDeath(); 395 } 396 } 397 398 @Override 399 public void onUnlockComplete() { 400 if (DEBUG) Log.d(TAG, "onUnlockComplete"); 401 if (mWrapper == null || !mWrapper.onUnlockComplete()) { 402 queueMessage(MSG_ON_UNLOCK_COMPLETE); 403 handleDeath(); 404 } 405 } 406 407 @Override 408 public IBinder asBinder() { 409 return mWrapper != null ? mWrapper.asBinder() : null; 410 } 411 412 @Override 413 public void binderDied() { 414 if (DEBUG) Log.d(TAG, "binderDeath"); 415 handleDeath(); 416 } 417 418 public interface TileChangeListener { 419 void onTileChanged(ComponentName tile); 420 } 421} 422