• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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