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