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.os.PowerWhitelistManager.REASON_TILE_ONCLICK; 19 import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; 20 import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT; 21 22 import static com.android.systemui.Flags.qsCustomTileClickGuaranteedBugFix; 23 24 import android.app.ActivityManager; 25 import android.app.compat.CompatChanges; 26 import android.content.BroadcastReceiver; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.ServiceConnection; 32 import android.content.pm.PackageManager; 33 import android.content.pm.ServiceInfo; 34 import android.net.Uri; 35 import android.os.Binder; 36 import android.os.Handler; 37 import android.os.IBinder; 38 import android.os.IDeviceIdleController; 39 import android.os.RemoteException; 40 import android.os.UserHandle; 41 import android.provider.DeviceConfig; 42 import android.service.quicksettings.IQSService; 43 import android.service.quicksettings.IQSTileService; 44 import android.service.quicksettings.TileService; 45 import android.text.format.DateUtils; 46 import android.util.ArraySet; 47 import android.util.Log; 48 49 import androidx.annotation.NonNull; 50 import androidx.annotation.Nullable; 51 import androidx.annotation.WorkerThread; 52 53 import com.android.systemui.Flags; 54 import com.android.systemui.broadcast.BroadcastDispatcher; 55 import com.android.systemui.dagger.qualifiers.Background; 56 import com.android.systemui.dagger.qualifiers.Main; 57 import com.android.systemui.util.concurrency.DelayableExecutor; 58 import com.android.systemui.util.time.SystemClock; 59 60 import dagger.assisted.Assisted; 61 import dagger.assisted.AssistedFactory; 62 import dagger.assisted.AssistedInject; 63 64 import java.util.NoSuchElementException; 65 import java.util.Objects; 66 import java.util.Optional; 67 import java.util.Set; 68 import java.util.concurrent.atomic.AtomicBoolean; 69 import java.util.function.Predicate; 70 71 /** 72 * Manages the lifecycle of a TileService. 73 * <p> 74 * Will keep track of all calls on the IQSTileService interface and will relay those calls to the 75 * TileService as soon as it is bound. It will only bind to the service when it is allowed to 76 * ({@link #setBindService(boolean)}) and when the service is available. 77 */ 78 public class TileLifecycleManager extends BroadcastReceiver implements 79 IQSTileService, ServiceConnection, IBinder.DeathRecipient { 80 81 private final boolean mDebug = Log.isLoggable(TAG, Log.DEBUG); 82 83 private static final String TAG = "TileLifecycleManager"; 84 85 private static final int META_DATA_QUERY_FLAGS = 86 PackageManager.GET_META_DATA 87 | PackageManager.MATCH_UNINSTALLED_PACKAGES 88 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 89 | PackageManager.MATCH_DIRECT_BOOT_AWARE; 90 91 private static final int MSG_ON_ADDED = 0; 92 private static final int MSG_ON_REMOVED = 1; 93 private static final int MSG_ON_CLICK = 2; 94 private static final int MSG_ON_UNLOCK_COMPLETE = 3; 95 private static final int MSG_ON_STOP_LISTENING = 4; 96 97 // Bind retry control. 98 private static final int MAX_BIND_RETRIES = 5; 99 private static final long DEFAULT_BIND_RETRY_DELAY = 5 * DateUtils.SECOND_IN_MILLIS; 100 private static final long ACTIVE_TILE_BIND_RETRY_DELAY = 1 * DateUtils.SECOND_IN_MILLIS; 101 private static final long LOW_MEMORY_BIND_RETRY_DELAY = 20 * DateUtils.SECOND_IN_MILLIS; 102 private static final long TILE_SERVICE_ONCLICK_ALLOW_LIST_DEFAULT_DURATION_MS = 15_000; 103 private static final String PROPERTY_TILE_SERVICE_ONCLICK_ALLOW_LIST_DURATION = 104 "property_tile_service_onclick_allow_list_duration"; 105 // Shared prefs that hold tile lifecycle info. 106 private static final String TILES = "tiles_prefs"; 107 108 private final Context mContext; 109 private final Handler mHandler; 110 private final Intent mIntent; 111 private final UserHandle mUser; 112 private final DelayableExecutor mExecutor; 113 private final SystemClock mSystemClock; 114 private final IBinder mToken = new Binder(); 115 private final PackageManagerAdapter mPackageManagerAdapter; 116 private final BroadcastDispatcher mBroadcastDispatcher; 117 private final ActivityManager mActivityManager; 118 private final IDeviceIdleController mDeviceIdleController; 119 120 private Set<Integer> mQueuedMessages = new ArraySet<>(); 121 @NonNull 122 private volatile Optional<QSTileServiceWrapper> mOptionalWrapper = Optional.empty(); 123 private boolean mListening; 124 private IBinder mClickBinder; 125 126 private int mBindTryCount; 127 private AtomicBoolean isDeathRebindScheduled = new AtomicBoolean(false); 128 private AtomicBoolean mBound = new AtomicBoolean(false); 129 private AtomicBoolean mPackageReceiverRegistered = new AtomicBoolean(false); 130 private AtomicBoolean mUserReceiverRegistered = new AtomicBoolean(false); 131 private AtomicBoolean mUnbindImmediate = new AtomicBoolean(false); 132 @Nullable 133 private TileChangeListener mChangeListener; 134 // Return value from bindServiceAsUser, determines whether safe to call unbind. 135 private AtomicBoolean mIsBound = new AtomicBoolean(false); 136 private long mTempAllowFgsLaunchDuration = TILE_SERVICE_ONCLICK_ALLOW_LIST_DEFAULT_DURATION_MS; 137 private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangedListener; 138 private AtomicBoolean mDeviceConfigChangedListenerRegistered = new AtomicBoolean(false); 139 140 @AssistedInject TileLifecycleManager(@ain Handler handler, Context context, IQSService service, PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher, @Assisted Intent intent, @Assisted UserHandle user, ActivityManager activityManager, IDeviceIdleController deviceIdleController, @Background DelayableExecutor executor, SystemClock systemClock)141 TileLifecycleManager(@Main Handler handler, Context context, IQSService service, 142 PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher, 143 @Assisted Intent intent, @Assisted UserHandle user, ActivityManager activityManager, 144 IDeviceIdleController deviceIdleController, @Background DelayableExecutor executor, 145 SystemClock systemClock) { 146 mContext = context; 147 mHandler = handler; 148 mIntent = intent; 149 mIntent.putExtra(TileService.EXTRA_SERVICE, service.asBinder()); 150 mIntent.putExtra(TileService.EXTRA_TOKEN, mToken); 151 mUser = user; 152 mExecutor = executor; 153 mSystemClock = systemClock; 154 mPackageManagerAdapter = packageManagerAdapter; 155 mBroadcastDispatcher = broadcastDispatcher; 156 mActivityManager = activityManager; 157 mDeviceIdleController = deviceIdleController; 158 mDeviceConfigChangedListener = properties -> { 159 if (!DeviceConfig.NAMESPACE_SYSTEMUI.equals(properties.getNamespace())) { 160 return; 161 } 162 mTempAllowFgsLaunchDuration = properties.getLong( 163 PROPERTY_TILE_SERVICE_ONCLICK_ALLOW_LIST_DURATION, 164 TILE_SERVICE_ONCLICK_ALLOW_LIST_DEFAULT_DURATION_MS); 165 }; 166 167 if (mDebug) Log.d(TAG, "Creating " + mIntent + " " + mUser); 168 } 169 170 /** Injectable factory for TileLifecycleManager. */ 171 @AssistedFactory 172 public interface Factory { 173 /** 174 * 175 */ create(Intent intent, UserHandle userHandle)176 TileLifecycleManager create(Intent intent, UserHandle userHandle); 177 } 178 getUserId()179 public int getUserId() { 180 return mUser.getIdentifier(); 181 } 182 getComponent()183 public ComponentName getComponent() { 184 return mIntent.getComponent(); 185 } 186 hasPendingClick()187 public boolean hasPendingClick() { 188 synchronized (mQueuedMessages) { 189 return mQueuedMessages.contains(MSG_ON_CLICK); 190 } 191 } 192 isActiveTile()193 public boolean isActiveTile() { 194 try { 195 ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), 196 META_DATA_QUERY_FLAGS, mUser.getIdentifier()); 197 return info != null && info.metaData != null 198 && info.metaData.getBoolean(TileService.META_DATA_ACTIVE_TILE, false); 199 } catch (RemoteException e) { 200 return false; 201 } 202 } 203 204 /** 205 * Determines whether the associated TileService is a Boolean Tile. 206 * 207 * @return true if {@link TileService#META_DATA_TOGGLEABLE_TILE} is set to {@code true} for this 208 * tile 209 * @see TileService#META_DATA_TOGGLEABLE_TILE 210 */ isToggleableTile()211 public boolean isToggleableTile() { 212 try { 213 ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), 214 META_DATA_QUERY_FLAGS, mUser.getIdentifier()); 215 return info != null && info.metaData != null 216 && info.metaData.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false); 217 } catch (RemoteException e) { 218 return false; 219 } 220 } 221 222 /** 223 * Binds just long enough to send any queued messages, then unbinds. 224 */ flushMessagesAndUnbind()225 public void flushMessagesAndUnbind() { 226 mExecutor.execute(() -> { 227 mUnbindImmediate.set(true); 228 setBindService(true); 229 }); 230 } 231 isBound()232 boolean isBound() { 233 return mBound.get(); 234 } 235 236 @WorkerThread setBindService(boolean bind)237 private void setBindService(boolean bind) { 238 if (mBound.get() && mUnbindImmediate.get()) { 239 // If we are already bound and expecting to unbind, this means we should stay bound 240 // because something else wants to hold the connection open. 241 mUnbindImmediate.set(false); 242 return; 243 } 244 mBound.set(bind); 245 if (bind) { 246 if (mDeviceConfigChangedListenerRegistered.compareAndSet(false, true)) { 247 DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI, mExecutor, 248 mDeviceConfigChangedListener); 249 mTempAllowFgsLaunchDuration = DeviceConfig.getLong(NAMESPACE_SYSTEMUI, 250 PROPERTY_TILE_SERVICE_ONCLICK_ALLOW_LIST_DURATION, 251 TILE_SERVICE_ONCLICK_ALLOW_LIST_DEFAULT_DURATION_MS); 252 } 253 if (mBindTryCount == MAX_BIND_RETRIES) { 254 // Too many failures, give up on this tile until an update. 255 startPackageListening(); 256 return; 257 } 258 if (!checkComponentState()) { 259 return; 260 } 261 if (mDebug) Log.d(TAG, "Binding service " + mIntent + " " + mUser); 262 mBindTryCount++; 263 try { 264 // Only try a new binding if we are not currently bound. 265 mIsBound.compareAndSet(false, bindServices()); 266 if (!mIsBound.get()) { 267 Log.d(TAG, "Failed to bind to service"); 268 mContext.unbindService(this); 269 } 270 } catch (SecurityException e) { 271 Log.e(TAG, "Failed to bind to service", e); 272 mIsBound.set(false); 273 } 274 } else { 275 unbindService(); 276 } 277 } 278 279 /** 280 * Binds or unbinds to IQSService 281 */ executeSetBindService(boolean bind)282 public void executeSetBindService(boolean bind) { 283 mExecutor.execute(() -> setBindService(bind)); 284 } 285 bindServices()286 private boolean bindServices() { 287 String packageName = mIntent.getComponent().getPackageName(); 288 int flags = Context.BIND_AUTO_CREATE 289 | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE 290 | Context.BIND_WAIVE_PRIORITY; 291 if (CompatChanges.isChangeEnabled(START_ACTIVITY_NEEDS_PENDING_INTENT, packageName, 292 mUser)) { 293 return mContext.bindServiceAsUser(mIntent, this, flags, mUser); 294 } 295 return mContext.bindServiceAsUser(mIntent, this, 296 flags | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, 297 mUser); 298 } 299 300 @WorkerThread unbindService()301 private void unbindService() { 302 if (mDebug) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser); 303 // Give it another chance next time it needs to be bound, out of kindness. 304 mBindTryCount = 0; 305 freeWrapper(); 306 if (mIsBound.get()) { 307 try { 308 mContext.unbindService(this); 309 } catch (Exception e) { 310 Log.e(TAG, "Failed to unbind service " 311 + mIntent.getComponent().flattenToShortString(), e); 312 } 313 mIsBound.set(false); 314 } 315 } 316 317 @Override onServiceConnected(ComponentName name, IBinder service)318 public void onServiceConnected(ComponentName name, IBinder service) { 319 if (mDebug) Log.d(TAG, "onServiceConnected " + name); 320 // Got a connection, set the binding count to 0. 321 mBindTryCount = 0; 322 final QSTileServiceWrapper wrapper = new QSTileServiceWrapper(Stub.asInterface(service)); 323 try { 324 service.linkToDeath(this, 0); 325 } catch (RemoteException e) { 326 } 327 mOptionalWrapper = Optional.of(wrapper); 328 handlePendingMessages(); 329 } 330 331 @Override onNullBinding(ComponentName name)332 public void onNullBinding(ComponentName name) { 333 executeSetBindService(false); 334 } 335 336 @Override onBindingDied(ComponentName name)337 public void onBindingDied(ComponentName name) { 338 if (mDebug) Log.d(TAG, "onBindingDied " + name); 339 handleDeath(); 340 } 341 342 @Override onServiceDisconnected(ComponentName name)343 public void onServiceDisconnected(ComponentName name) { 344 if (mDebug) Log.d(TAG, "onServiceDisconnected " + name); 345 freeWrapper(); 346 } 347 handlePendingMessages()348 private void handlePendingMessages() { 349 // This ordering is laid out manually to make sure we preserve the TileService 350 // lifecycle. 351 ArraySet<Integer> queue; 352 synchronized (mQueuedMessages) { 353 queue = new ArraySet<>(mQueuedMessages); 354 mQueuedMessages.clear(); 355 } 356 if (queue.contains(MSG_ON_ADDED)) { 357 if (mDebug) Log.d(TAG, "Handling pending onAdded " + getComponent()); 358 onTileAdded(); 359 } 360 if (mListening) { 361 if (mDebug) Log.d(TAG, "Handling pending onStartListening " + getComponent()); 362 onStartListening(); 363 } 364 if (queue.contains(MSG_ON_CLICK)) { 365 if (mDebug) Log.d(TAG, "Handling pending onClick " + getComponent()); 366 if (!mListening) { 367 Log.w(TAG, "Managed to get click on non-listening state... " + getComponent()); 368 // Skipping click since lost click privileges. 369 } else { 370 onClick(mClickBinder); 371 } 372 } 373 if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) { 374 if (mDebug) Log.d(TAG, "Handling pending onUnlockComplete " + getComponent()); 375 if (!mListening) { 376 Log.w(TAG, 377 "Managed to get unlock on non-listening state... " + getComponent()); 378 // Skipping unlock since lost click privileges. 379 } else { 380 onUnlockComplete(); 381 } 382 } 383 if (qsCustomTileClickGuaranteedBugFix()) { 384 if (queue.contains(MSG_ON_STOP_LISTENING)) { 385 if (mDebug) Log.d(TAG, "Handling pending onStopListening " + getComponent()); 386 if (mListening) { 387 onStopListening(); 388 } else { 389 Log.w(TAG, "Trying to stop listening when not listening " + getComponent()); 390 } 391 } 392 } 393 if (queue.contains(MSG_ON_REMOVED)) { 394 if (mDebug) Log.d(TAG, "Handling pending onRemoved " + getComponent()); 395 if (mListening) { 396 Log.w(TAG, "Managed to get remove in listening state... " + getComponent()); 397 onStopListening(); 398 } 399 onTileRemoved(); 400 } 401 mExecutor.execute(() -> { 402 if (mUnbindImmediate.get()) { 403 mUnbindImmediate.set(false); 404 setBindService(false); 405 } 406 }); 407 } 408 handleDestroy()409 public void handleDestroy() { 410 if (mDebug) Log.d(TAG, "handleDestroy"); 411 if (mPackageReceiverRegistered.get() || mUserReceiverRegistered.get()) { 412 stopPackageListening(); 413 } 414 mChangeListener = null; 415 if (mDeviceConfigChangedListener != null) { 416 DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener); 417 } 418 } 419 420 /** 421 * Handles a dead binder. 422 * 423 * It means that we need to clean up the binding (calling unbindService). After that, if we 424 * are supposed to be bound, we will try to bind after some amount of time. 425 */ handleDeath()426 private void handleDeath() { 427 if (!mIsBound.get()) { 428 // If we are already not bound, don't do anything else. 429 return; 430 } 431 mExecutor.execute(() -> { 432 if (!mIsBound.get()) { 433 // If we are already not bound, don't do anything else. 434 return; 435 } 436 // Clearly we shouldn't be bound anymore 437 if (mDebug) Log.d(TAG, "handleDeath " + getComponent()); 438 // Binder died, make sure that we unbind. However, we don't want to call setBindService 439 // as we still may want to rebind. 440 unbindService(); 441 // If mBound is true (meaning that we should be bound), then reschedule binding for 442 // later. 443 if (mBound.get() && checkComponentState()) { 444 if (isDeathRebindScheduled.compareAndSet(false, true)) { // if already not scheduled 445 446 447 mExecutor.executeDelayed(() -> { 448 // Only rebind if we are supposed to, but remove the scheduling anyway. 449 if (mBound.get()) { 450 setBindService(true); 451 } 452 isDeathRebindScheduled.set(false); // allow scheduling again 453 }, getRebindDelay()); 454 } 455 } 456 }); 457 } 458 459 private long mLastRebind = 0; 460 /** 461 * @return the delay to automatically rebind after a service died. It provides a longer delay if 462 * the device is a low memory state because the service is likely to get killed again by the 463 * system. In this case we want to rebind later and not to cause a loop of a frequent rebinds. 464 * It also provides a longer delay if called quickly (a few seconds) after a first call. 465 */ getRebindDelay()466 private long getRebindDelay() { 467 final long now = mSystemClock.currentTimeMillis(); 468 469 final ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo(); 470 mActivityManager.getMemoryInfo(info); 471 472 final long delay; 473 if (info.lowMemory) { 474 delay = LOW_MEMORY_BIND_RETRY_DELAY; 475 } else { 476 if (Flags.qsQuickRebindActiveTiles()) { 477 final long elapsedTimeSinceLastRebind = now - mLastRebind; 478 final boolean justAttemptedRebind = 479 elapsedTimeSinceLastRebind < DEFAULT_BIND_RETRY_DELAY; 480 if (isActiveTile() && !justAttemptedRebind) { 481 delay = ACTIVE_TILE_BIND_RETRY_DELAY; 482 } else { 483 delay = DEFAULT_BIND_RETRY_DELAY; 484 } 485 } else { 486 delay = DEFAULT_BIND_RETRY_DELAY; 487 } 488 489 mLastRebind = now; 490 } 491 if (mDebug) Log.i(TAG, "Rebinding with a delay=" + delay + " - " + getComponent()); 492 return delay; 493 } 494 495 private boolean checkComponentState() { 496 if (!isPackageAvailable() || !isComponentAvailable()) { 497 startPackageListening(); 498 return false; 499 } 500 return true; 501 } 502 503 private void startPackageListening() { 504 if (mDebug) Log.d(TAG, "startPackageListening " + getComponent()); 505 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 506 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 507 filter.addDataScheme("package"); 508 try { 509 mPackageReceiverRegistered.set(true); 510 mContext.registerReceiverAsUser( 511 this, mUser, filter, null, mHandler, Context.RECEIVER_EXPORTED); 512 } catch (Exception ex) { 513 mPackageReceiverRegistered.set(false); 514 Log.e(TAG, "Could not register package receiver " + getComponent(), ex); 515 } 516 filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); 517 try { 518 mUserReceiverRegistered.set(true); 519 mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler, mUser); 520 } catch (Exception ex) { 521 mUserReceiverRegistered.set(false); 522 Log.e(TAG, "Could not register unlock receiver " + getComponent(), ex); 523 } 524 } 525 526 private void stopPackageListening() { 527 if (mDebug) Log.d(TAG, "stopPackageListening " + getComponent()); 528 if (mUserReceiverRegistered.compareAndSet(true, false)) { 529 mBroadcastDispatcher.unregisterReceiver(this); 530 } 531 if (mPackageReceiverRegistered.compareAndSet(true, false)) { 532 mContext.unregisterReceiver(this); 533 } 534 } 535 536 public void setTileChangeListener(TileChangeListener changeListener) { 537 mChangeListener = changeListener; 538 } 539 540 @Override 541 public void onReceive(Context context, Intent intent) { 542 if (mDebug) Log.d(TAG, "onReceive: " + intent); 543 if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { 544 Uri data = intent.getData(); 545 String pkgName = data.getEncodedSchemeSpecificPart(); 546 if (!Objects.equals(pkgName, mIntent.getComponent().getPackageName())) { 547 return; 548 } 549 } 550 if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) && mChangeListener != null) { 551 mChangeListener.onTileChanged(mIntent.getComponent()); 552 } 553 stopPackageListening(); 554 mExecutor.execute(() -> { 555 if (mBound.get()) { 556 // Trying to bind again will check the state of the package before bothering to 557 // bind. 558 if (mDebug) Log.d(TAG, "Trying to rebind " + getComponent()); 559 setBindService(true); 560 } 561 562 }); 563 } 564 isComponentAvailable()565 private boolean isComponentAvailable() { 566 String packageName = mIntent.getComponent().getPackageName(); 567 try { 568 ServiceInfo si = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), 569 0, mUser.getIdentifier()); 570 if (mDebug && si == null) { 571 Log.d(TAG, "Can't find component " + mIntent.getComponent()); 572 } 573 return si != null; 574 } catch (RemoteException e) { 575 // Shouldn't happen. 576 } 577 return false; 578 } 579 isPackageAvailable()580 private boolean isPackageAvailable() { 581 String packageName = mIntent.getComponent().getPackageName(); 582 try { 583 mPackageManagerAdapter.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier()); 584 return true; 585 } catch (PackageManager.NameNotFoundException e) { 586 if (mDebug) { 587 Log.d(TAG, "Package not available: " + packageName, e); 588 } else { 589 Log.d(TAG, "Package not available: " + packageName); 590 } 591 } 592 return false; 593 } 594 queueMessage(int message)595 private void queueMessage(int message) { 596 synchronized (mQueuedMessages) { 597 mQueuedMessages.add(message); 598 } 599 } 600 601 @Override onTileAdded()602 public void onTileAdded() { 603 if (mDebug) Log.d(TAG, "onTileAdded " + getComponent()); 604 if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onTileAdded)) { 605 queueMessage(MSG_ON_ADDED); 606 handleDeath(); 607 } 608 } 609 610 @Override onTileRemoved()611 public void onTileRemoved() { 612 if (mDebug) Log.d(TAG, "onTileRemoved " + getComponent()); 613 if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onTileRemoved)) { 614 queueMessage(MSG_ON_REMOVED); 615 handleDeath(); 616 } 617 } 618 619 @Override onStartListening()620 public void onStartListening() { 621 if (mDebug) Log.d(TAG, "onStartListening " + getComponent()); 622 mListening = true; 623 if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStartListening)) { 624 handleDeath(); 625 } 626 } 627 628 @Override onStopListening()629 public void onStopListening() { 630 if (qsCustomTileClickGuaranteedBugFix() && hasPendingClick()) { 631 Log.d(TAG, "Enqueue stop listening"); 632 queueMessage(MSG_ON_STOP_LISTENING); 633 } else { 634 if (mDebug) Log.d(TAG, "onStopListening " + getComponent()); 635 mListening = false; 636 if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStopListening)) { 637 handleDeath(); 638 } 639 } 640 } 641 642 @Override onClick(IBinder iBinder)643 public void onClick(IBinder iBinder) { 644 if (mDebug) Log.d(TAG, "onClick " + iBinder + " " + getComponent() + " " + mUser); 645 if (isNullOrFailedAction(mOptionalWrapper, (wrapper) -> { 646 final String packageName = mIntent.getComponent().getPackageName(); 647 try { 648 mDeviceIdleController.addPowerSaveTempWhitelistApp(packageName, 649 mTempAllowFgsLaunchDuration, mUser.getIdentifier(), REASON_TILE_ONCLICK, 650 "tile onclick"); 651 } catch (RemoteException e) { 652 Log.d(TAG, "Caught exception trying to add client package to temp allow list", e); 653 } 654 return wrapper.onClick(iBinder); 655 })) { 656 mClickBinder = iBinder; 657 queueMessage(MSG_ON_CLICK); 658 handleDeath(); 659 } 660 } 661 662 @Override onUnlockComplete()663 public void onUnlockComplete() { 664 if (mDebug) Log.d(TAG, "onUnlockComplete " + getComponent()); 665 if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onUnlockComplete)) { 666 queueMessage(MSG_ON_UNLOCK_COMPLETE); 667 handleDeath(); 668 } 669 } 670 671 @Nullable 672 @Override asBinder()673 public IBinder asBinder() { 674 return mOptionalWrapper.map(QSTileServiceWrapper::asBinder).orElse(null); 675 } 676 677 @Override binderDied()678 public void binderDied() { 679 if (mDebug) Log.d(TAG, "binderDeath " + getComponent()); 680 handleDeath(); 681 } 682 getToken()683 public IBinder getToken() { 684 return mToken; 685 } 686 freeWrapper()687 private void freeWrapper() { 688 if (mOptionalWrapper.isPresent()) { 689 try { 690 mOptionalWrapper.ifPresent( 691 (wrapper) -> wrapper.asBinder().unlinkToDeath(this, 0) 692 ); 693 } catch (NoSuchElementException e) { 694 Log.w(TAG, "Trying to unlink not linked recipient for component" 695 + mIntent.getComponent().flattenToShortString()); 696 } 697 mOptionalWrapper = Optional.empty(); 698 } 699 } 700 701 public interface TileChangeListener { 702 void onTileChanged(ComponentName tile); 703 } 704 705 /** 706 * Returns true if the Optional is empty OR performing the action on the content of the Optional 707 * (when not empty) fails. 708 */ isNullOrFailedAction( Optional<QSTileServiceWrapper> optionalWrapper, Predicate<QSTileServiceWrapper> action )709 private static boolean isNullOrFailedAction( 710 Optional<QSTileServiceWrapper> optionalWrapper, 711 Predicate<QSTileServiceWrapper> action 712 ) { 713 return !optionalWrapper.map(action::test).orElse(false); 714 } 715 716 /** 717 * Returns true if the Optional is not empty AND performing the action on the content of 718 * the Optional fails. 719 */ isNotNullAndFailedAction( Optional<QSTileServiceWrapper> optionalWrapper, Predicate<QSTileServiceWrapper> action )720 private static boolean isNotNullAndFailedAction( 721 Optional<QSTileServiceWrapper> optionalWrapper, 722 Predicate<QSTileServiceWrapper> action 723 ) { 724 return !optionalWrapper.map(action::test).orElse(true); 725 } 726 } 727