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