• 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.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