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