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 */ 16 package com.android.systemui.qs.external; 17 18 import android.content.BroadcastReceiver; 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.content.ServiceConnection; 24 import android.content.pm.PackageManager; 25 import android.content.pm.ServiceInfo; 26 import android.net.Uri; 27 import android.os.Binder; 28 import android.os.Handler; 29 import android.os.IBinder; 30 import android.os.RemoteException; 31 import android.os.UserHandle; 32 import android.service.quicksettings.IQSService; 33 import android.service.quicksettings.IQSTileService; 34 import android.service.quicksettings.TileService; 35 import android.util.ArraySet; 36 import android.util.Log; 37 38 import androidx.annotation.Nullable; 39 import androidx.annotation.WorkerThread; 40 41 import com.android.systemui.broadcast.BroadcastDispatcher; 42 import com.android.systemui.dagger.qualifiers.Main; 43 44 import java.util.NoSuchElementException; 45 import java.util.Objects; 46 import java.util.Set; 47 import java.util.concurrent.atomic.AtomicBoolean; 48 49 import dagger.assisted.Assisted; 50 import dagger.assisted.AssistedFactory; 51 import dagger.assisted.AssistedInject; 52 53 /** 54 * Manages the lifecycle of a TileService. 55 * <p> 56 * Will keep track of all calls on the IQSTileService interface and will relay those calls to the 57 * TileService as soon as it is bound. It will only bind to the service when it is allowed to 58 * ({@link #setBindService(boolean)}) and when the service is available. 59 */ 60 public class TileLifecycleManager extends BroadcastReceiver implements 61 IQSTileService, ServiceConnection, IBinder.DeathRecipient { 62 public static final boolean DEBUG = false; 63 64 private static final String TAG = "TileLifecycleManager"; 65 66 private static final int META_DATA_QUERY_FLAGS = 67 PackageManager.GET_META_DATA 68 | PackageManager.MATCH_UNINSTALLED_PACKAGES 69 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 70 | PackageManager.MATCH_DIRECT_BOOT_AWARE; 71 72 private static final int MSG_ON_ADDED = 0; 73 private static final int MSG_ON_REMOVED = 1; 74 private static final int MSG_ON_CLICK = 2; 75 private static final int MSG_ON_UNLOCK_COMPLETE = 3; 76 77 // Bind retry control. 78 private static final int MAX_BIND_RETRIES = 5; 79 private static final int DEFAULT_BIND_RETRY_DELAY = 1000; 80 81 // Shared prefs that hold tile lifecycle info. 82 private static final String TILES = "tiles_prefs"; 83 84 private final Context mContext; 85 private final Handler mHandler; 86 private final Intent mIntent; 87 private final UserHandle mUser; 88 private final IBinder mToken = new Binder(); 89 private final PackageManagerAdapter mPackageManagerAdapter; 90 private final BroadcastDispatcher mBroadcastDispatcher; 91 92 private Set<Integer> mQueuedMessages = new ArraySet<>(); 93 @Nullable 94 private QSTileServiceWrapper mWrapper; 95 private boolean mListening; 96 private IBinder mClickBinder; 97 98 private int mBindTryCount; 99 private int mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY; 100 private boolean mBound; 101 private AtomicBoolean mPackageReceiverRegistered = new AtomicBoolean(false); 102 private AtomicBoolean mUserReceiverRegistered = new AtomicBoolean(false); 103 private boolean mUnbindImmediate; 104 @Nullable 105 private TileChangeListener mChangeListener; 106 // Return value from bindServiceAsUser, determines whether safe to call unbind. 107 private boolean mIsBound; 108 109 @AssistedInject TileLifecycleManager(@ain Handler handler, Context context, IQSService service, PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher, @Assisted Intent intent, @Assisted UserHandle user)110 TileLifecycleManager(@Main Handler handler, Context context, IQSService service, 111 PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher, 112 @Assisted Intent intent, @Assisted UserHandle user) { 113 mContext = context; 114 mHandler = handler; 115 mIntent = intent; 116 mIntent.putExtra(TileService.EXTRA_SERVICE, service.asBinder()); 117 mIntent.putExtra(TileService.EXTRA_TOKEN, mToken); 118 mUser = user; 119 mPackageManagerAdapter = packageManagerAdapter; 120 mBroadcastDispatcher = broadcastDispatcher; 121 if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser); 122 } 123 124 /** Injectable factory for TileLifecycleManager. */ 125 @AssistedFactory 126 public interface Factory { 127 /** */ create(Intent intent, UserHandle userHandle)128 TileLifecycleManager create(Intent intent, UserHandle userHandle); 129 } 130 getUserId()131 public int getUserId() { 132 return mUser.getIdentifier(); 133 } 134 getComponent()135 public ComponentName getComponent() { 136 return mIntent.getComponent(); 137 } 138 hasPendingClick()139 public boolean hasPendingClick() { 140 synchronized (mQueuedMessages) { 141 return mQueuedMessages.contains(MSG_ON_CLICK); 142 } 143 } 144 setBindRetryDelay(int delayMs)145 public void setBindRetryDelay(int delayMs) { 146 mBindRetryDelay = delayMs; 147 } 148 isActiveTile()149 public boolean isActiveTile() { 150 try { 151 ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), 152 META_DATA_QUERY_FLAGS); 153 return info.metaData != null 154 && info.metaData.getBoolean(TileService.META_DATA_ACTIVE_TILE, false); 155 } catch (PackageManager.NameNotFoundException e) { 156 return false; 157 } 158 } 159 160 /** 161 * Determines whether the associated TileService is a Boolean Tile. 162 * 163 * @return true if {@link TileService#META_DATA_TOGGLEABLE_TILE} is set to {@code true} for this 164 * tile 165 * @see TileService#META_DATA_TOGGLEABLE_TILE 166 */ isToggleableTile()167 public boolean isToggleableTile() { 168 try { 169 ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), 170 META_DATA_QUERY_FLAGS); 171 return info.metaData != null 172 && info.metaData.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false); 173 } catch (PackageManager.NameNotFoundException e) { 174 return false; 175 } 176 } 177 178 /** 179 * Binds just long enough to send any queued messages, then unbinds. 180 */ flushMessagesAndUnbind()181 public void flushMessagesAndUnbind() { 182 mUnbindImmediate = true; 183 setBindService(true); 184 } 185 186 /** 187 * Binds or unbinds to IQSService 188 */ 189 @WorkerThread setBindService(boolean bind)190 public void setBindService(boolean bind) { 191 if (mBound && mUnbindImmediate) { 192 // If we are already bound and expecting to unbind, this means we should stay bound 193 // because something else wants to hold the connection open. 194 mUnbindImmediate = false; 195 return; 196 } 197 mBound = bind; 198 if (bind) { 199 if (mBindTryCount == MAX_BIND_RETRIES) { 200 // Too many failures, give up on this tile until an update. 201 startPackageListening(); 202 return; 203 } 204 if (!checkComponentState()) { 205 return; 206 } 207 if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser); 208 mBindTryCount++; 209 try { 210 mIsBound = mContext.bindServiceAsUser(mIntent, this, 211 Context.BIND_AUTO_CREATE 212 | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE 213 | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS 214 | Context.BIND_WAIVE_PRIORITY, 215 mUser); 216 if (!mIsBound) { 217 mContext.unbindService(this); 218 } 219 } catch (SecurityException e) { 220 Log.e(TAG, "Failed to bind to service", e); 221 mIsBound = false; 222 } 223 } else { 224 if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser); 225 // Give it another chance next time it needs to be bound, out of kindness. 226 mBindTryCount = 0; 227 freeWrapper(); 228 if (mIsBound) { 229 try { 230 mContext.unbindService(this); 231 } catch (Exception e) { 232 Log.e(TAG, "Failed to unbind service " 233 + mIntent.getComponent().flattenToShortString(), e); 234 } 235 mIsBound = false; 236 } 237 } 238 } 239 240 @Override onServiceConnected(ComponentName name, IBinder service)241 public void onServiceConnected(ComponentName name, IBinder service) { 242 if (DEBUG) Log.d(TAG, "onServiceConnected " + name); 243 // Got a connection, set the binding count to 0. 244 mBindTryCount = 0; 245 final QSTileServiceWrapper wrapper = new QSTileServiceWrapper(Stub.asInterface(service)); 246 try { 247 service.linkToDeath(this, 0); 248 } catch (RemoteException e) { 249 } 250 mWrapper = wrapper; 251 handlePendingMessages(); 252 } 253 254 @Override onServiceDisconnected(ComponentName name)255 public void onServiceDisconnected(ComponentName name) { 256 if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name); 257 handleDeath(); 258 } 259 handlePendingMessages()260 private void handlePendingMessages() { 261 // This ordering is laid out manually to make sure we preserve the TileService 262 // lifecycle. 263 ArraySet<Integer> queue; 264 synchronized (mQueuedMessages) { 265 queue = new ArraySet<>(mQueuedMessages); 266 mQueuedMessages.clear(); 267 } 268 if (queue.contains(MSG_ON_ADDED)) { 269 if (DEBUG) Log.d(TAG, "Handling pending onAdded"); 270 onTileAdded(); 271 } 272 if (mListening) { 273 if (DEBUG) Log.d(TAG, "Handling pending onStartListening"); 274 onStartListening(); 275 } 276 if (queue.contains(MSG_ON_CLICK)) { 277 if (DEBUG) Log.d(TAG, "Handling pending onClick"); 278 if (!mListening) { 279 Log.w(TAG, "Managed to get click on non-listening state..."); 280 // Skipping click since lost click privileges. 281 } else { 282 onClick(mClickBinder); 283 } 284 } 285 if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) { 286 if (DEBUG) Log.d(TAG, "Handling pending onUnlockComplete"); 287 if (!mListening) { 288 Log.w(TAG, "Managed to get unlock on non-listening state..."); 289 // Skipping unlock since lost click privileges. 290 } else { 291 onUnlockComplete(); 292 } 293 } 294 if (queue.contains(MSG_ON_REMOVED)) { 295 if (DEBUG) Log.d(TAG, "Handling pending onRemoved"); 296 if (mListening) { 297 Log.w(TAG, "Managed to get remove in listening state..."); 298 onStopListening(); 299 } 300 onTileRemoved(); 301 } 302 if (mUnbindImmediate) { 303 mUnbindImmediate = false; 304 setBindService(false); 305 } 306 } 307 handleDestroy()308 public void handleDestroy() { 309 if (DEBUG) Log.d(TAG, "handleDestroy"); 310 if (mPackageReceiverRegistered.get() || mUserReceiverRegistered.get()) { 311 stopPackageListening(); 312 } 313 mChangeListener = null; 314 } 315 handleDeath()316 private void handleDeath() { 317 if (mWrapper == null) return; 318 freeWrapper(); 319 // Clearly not bound anymore 320 mIsBound = false; 321 if (!mBound) return; 322 if (DEBUG) Log.d(TAG, "handleDeath"); 323 if (checkComponentState()) { 324 mHandler.postDelayed(new Runnable() { 325 @Override 326 public void run() { 327 if (mBound) { 328 // Retry binding. 329 setBindService(true); 330 } 331 } 332 }, mBindRetryDelay); 333 } 334 } 335 checkComponentState()336 private boolean checkComponentState() { 337 if (!isPackageAvailable() || !isComponentAvailable()) { 338 startPackageListening(); 339 return false; 340 } 341 return true; 342 } 343 startPackageListening()344 private void startPackageListening() { 345 if (DEBUG) Log.d(TAG, "startPackageListening"); 346 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 347 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 348 filter.addDataScheme("package"); 349 try { 350 mPackageReceiverRegistered.set(true); 351 mContext.registerReceiverAsUser( 352 this, mUser, filter, null, mHandler, Context.RECEIVER_EXPORTED); 353 } catch (Exception ex) { 354 mPackageReceiverRegistered.set(false); 355 Log.e(TAG, "Could not register package receiver", ex); 356 } 357 filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); 358 try { 359 mUserReceiverRegistered.set(true); 360 mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler, mUser); 361 } catch (Exception ex) { 362 mUserReceiverRegistered.set(false); 363 Log.e(TAG, "Could not register unlock receiver", ex); 364 } 365 } 366 stopPackageListening()367 private void stopPackageListening() { 368 if (DEBUG) Log.d(TAG, "stopPackageListening"); 369 if (mUserReceiverRegistered.compareAndSet(true, false)) { 370 mBroadcastDispatcher.unregisterReceiver(this); 371 } 372 if (mPackageReceiverRegistered.compareAndSet(true, false)) { 373 mContext.unregisterReceiver(this); 374 } 375 } 376 setTileChangeListener(TileChangeListener changeListener)377 public void setTileChangeListener(TileChangeListener changeListener) { 378 mChangeListener = changeListener; 379 } 380 381 @Override onReceive(Context context, Intent intent)382 public void onReceive(Context context, Intent intent) { 383 if (DEBUG) Log.d(TAG, "onReceive: " + intent); 384 if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { 385 Uri data = intent.getData(); 386 String pkgName = data.getEncodedSchemeSpecificPart(); 387 if (!Objects.equals(pkgName, mIntent.getComponent().getPackageName())) { 388 return; 389 } 390 } 391 if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) && mChangeListener != null) { 392 mChangeListener.onTileChanged(mIntent.getComponent()); 393 } 394 stopPackageListening(); 395 if (mBound) { 396 // Trying to bind again will check the state of the package before bothering to bind. 397 if (DEBUG) Log.d(TAG, "Trying to rebind"); 398 setBindService(true); 399 } 400 } 401 isComponentAvailable()402 private boolean isComponentAvailable() { 403 String packageName = mIntent.getComponent().getPackageName(); 404 try { 405 ServiceInfo si = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), 406 0, mUser.getIdentifier()); 407 if (DEBUG && si == null) Log.d(TAG, "Can't find component " + mIntent.getComponent()); 408 return si != null; 409 } catch (RemoteException e) { 410 // Shouldn't happen. 411 } 412 return false; 413 } 414 isPackageAvailable()415 private boolean isPackageAvailable() { 416 String packageName = mIntent.getComponent().getPackageName(); 417 try { 418 mPackageManagerAdapter.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier()); 419 return true; 420 } catch (PackageManager.NameNotFoundException e) { 421 if (DEBUG) Log.d(TAG, "Package not available: " + packageName, e); 422 else Log.d(TAG, "Package not available: " + packageName); 423 } 424 return false; 425 } 426 queueMessage(int message)427 private void queueMessage(int message) { 428 synchronized (mQueuedMessages) { 429 mQueuedMessages.add(message); 430 } 431 } 432 433 @Override onTileAdded()434 public void onTileAdded() { 435 if (DEBUG) Log.d(TAG, "onTileAdded"); 436 if (mWrapper == null || !mWrapper.onTileAdded()) { 437 queueMessage(MSG_ON_ADDED); 438 handleDeath(); 439 } 440 } 441 442 @Override onTileRemoved()443 public void onTileRemoved() { 444 if (DEBUG) Log.d(TAG, "onTileRemoved"); 445 if (mWrapper == null || !mWrapper.onTileRemoved()) { 446 queueMessage(MSG_ON_REMOVED); 447 handleDeath(); 448 } 449 } 450 451 @Override onStartListening()452 public void onStartListening() { 453 if (DEBUG) Log.d(TAG, "onStartListening"); 454 mListening = true; 455 if (mWrapper != null && !mWrapper.onStartListening()) { 456 handleDeath(); 457 } 458 } 459 460 @Override onStopListening()461 public void onStopListening() { 462 if (DEBUG) Log.d(TAG, "onStopListening"); 463 mListening = false; 464 if (mWrapper != null && !mWrapper.onStopListening()) { 465 handleDeath(); 466 } 467 } 468 469 @Override onClick(IBinder iBinder)470 public void onClick(IBinder iBinder) { 471 if (DEBUG) Log.d(TAG, "onClick " + iBinder + " " + mUser); 472 if (mWrapper == null || !mWrapper.onClick(iBinder)) { 473 mClickBinder = iBinder; 474 queueMessage(MSG_ON_CLICK); 475 handleDeath(); 476 } 477 } 478 479 @Override onUnlockComplete()480 public void onUnlockComplete() { 481 if (DEBUG) Log.d(TAG, "onUnlockComplete"); 482 if (mWrapper == null || !mWrapper.onUnlockComplete()) { 483 queueMessage(MSG_ON_UNLOCK_COMPLETE); 484 handleDeath(); 485 } 486 } 487 488 @Nullable 489 @Override asBinder()490 public IBinder asBinder() { 491 return mWrapper != null ? mWrapper.asBinder() : null; 492 } 493 494 @Override binderDied()495 public void binderDied() { 496 if (DEBUG) Log.d(TAG, "binderDeath"); 497 handleDeath(); 498 } 499 getToken()500 public IBinder getToken() { 501 return mToken; 502 } 503 freeWrapper()504 private void freeWrapper() { 505 if (mWrapper != null) { 506 try { 507 mWrapper.asBinder().unlinkToDeath(this, 0); 508 } catch (NoSuchElementException e) { 509 Log.w(TAG, "Trying to unlink not linked recipient for component" 510 + mIntent.getComponent().flattenToShortString()); 511 } 512 mWrapper = null; 513 } 514 } 515 516 public interface TileChangeListener { onTileChanged(ComponentName tile)517 void onTileChanged(ComponentName tile); 518 } 519 } 520