• 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.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
onServiceDisconnected(ComponentName name)284     public void onServiceDisconnected(ComponentName name) {
285         if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name);
286         handleDeath();
287     }
288 
handlePendingMessages()289     private void handlePendingMessages() {
290         // This ordering is laid out manually to make sure we preserve the TileService
291         // lifecycle.
292         ArraySet<Integer> queue;
293         synchronized (mQueuedMessages) {
294             queue = new ArraySet<>(mQueuedMessages);
295             mQueuedMessages.clear();
296         }
297         if (queue.contains(MSG_ON_ADDED)) {
298             if (DEBUG) Log.d(TAG, "Handling pending onAdded");
299             onTileAdded();
300         }
301         if (mListening) {
302             if (DEBUG) Log.d(TAG, "Handling pending onStartListening");
303             onStartListening();
304         }
305         if (queue.contains(MSG_ON_CLICK)) {
306             if (DEBUG) Log.d(TAG, "Handling pending onClick");
307             if (!mListening) {
308                 Log.w(TAG, "Managed to get click on non-listening state...");
309                 // Skipping click since lost click privileges.
310             } else {
311                 onClick(mClickBinder);
312             }
313         }
314         if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) {
315             if (DEBUG) Log.d(TAG, "Handling pending onUnlockComplete");
316             if (!mListening) {
317                 Log.w(TAG, "Managed to get unlock on non-listening state...");
318                 // Skipping unlock since lost click privileges.
319             } else {
320                 onUnlockComplete();
321             }
322         }
323         if (queue.contains(MSG_ON_REMOVED)) {
324             if (DEBUG) Log.d(TAG, "Handling pending onRemoved");
325             if (mListening) {
326                 Log.w(TAG, "Managed to get remove in listening state...");
327                 onStopListening();
328             }
329             onTileRemoved();
330         }
331         mExecutor.execute(() -> {
332             if (mUnbindImmediate.get()) {
333                 mUnbindImmediate.set(false);
334                 setBindService(false);
335             }
336         });
337     }
338 
handleDestroy()339     public void handleDestroy() {
340         if (DEBUG) Log.d(TAG, "handleDestroy");
341         if (mPackageReceiverRegistered.get() || mUserReceiverRegistered.get()) {
342             stopPackageListening();
343         }
344         mChangeListener = null;
345     }
346 
handleDeath()347     private void handleDeath() {
348         if (mWrapper == null) return;
349         freeWrapper();
350         // Clearly not bound anymore
351         mIsBound.set(false);
352         if (!mBound.get()) return;
353         if (DEBUG) Log.d(TAG, "handleDeath");
354         if (checkComponentState()) {
355             mExecutor.executeDelayed(() -> setBindService(true), mBindRetryDelay);
356         }
357     }
358 
checkComponentState()359     private boolean checkComponentState() {
360         if (!isPackageAvailable() || !isComponentAvailable()) {
361             startPackageListening();
362             return false;
363         }
364         return true;
365     }
366 
startPackageListening()367     private void startPackageListening() {
368         if (DEBUG) Log.d(TAG, "startPackageListening");
369         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
370         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
371         filter.addDataScheme("package");
372         try {
373             mPackageReceiverRegistered.set(true);
374             mContext.registerReceiverAsUser(
375                     this, mUser, filter, null, mHandler, Context.RECEIVER_EXPORTED);
376         } catch (Exception ex) {
377             mPackageReceiverRegistered.set(false);
378             Log.e(TAG, "Could not register package receiver", ex);
379         }
380         filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
381         try {
382             mUserReceiverRegistered.set(true);
383             mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler, mUser);
384         } catch (Exception ex) {
385             mUserReceiverRegistered.set(false);
386             Log.e(TAG, "Could not register unlock receiver", ex);
387         }
388     }
389 
stopPackageListening()390     private void stopPackageListening() {
391         if (DEBUG) Log.d(TAG, "stopPackageListening");
392         if (mUserReceiverRegistered.compareAndSet(true, false)) {
393             mBroadcastDispatcher.unregisterReceiver(this);
394         }
395         if (mPackageReceiverRegistered.compareAndSet(true, false)) {
396             mContext.unregisterReceiver(this);
397         }
398     }
399 
setTileChangeListener(TileChangeListener changeListener)400     public void setTileChangeListener(TileChangeListener changeListener) {
401         mChangeListener = changeListener;
402     }
403 
404     @Override
onReceive(Context context, Intent intent)405     public void onReceive(Context context, Intent intent) {
406         if (DEBUG) Log.d(TAG, "onReceive: " + intent);
407         if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
408             Uri data = intent.getData();
409             String pkgName = data.getEncodedSchemeSpecificPart();
410             if (!Objects.equals(pkgName, mIntent.getComponent().getPackageName())) {
411                 return;
412             }
413         }
414         if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) && mChangeListener != null) {
415             mChangeListener.onTileChanged(mIntent.getComponent());
416         }
417         stopPackageListening();
418         mExecutor.execute(() -> {
419             if (mBound.get()) {
420                 // Trying to bind again will check the state of the package before bothering to
421                 // bind.
422                 if (DEBUG) Log.d(TAG, "Trying to rebind");
423                 setBindService(true);
424             }
425 
426         });
427     }
428 
isComponentAvailable()429     private boolean isComponentAvailable() {
430         String packageName = mIntent.getComponent().getPackageName();
431         try {
432             ServiceInfo si = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
433                     0, mUser.getIdentifier());
434             if (DEBUG && si == null) Log.d(TAG, "Can't find component " + mIntent.getComponent());
435             return si != null;
436         } catch (RemoteException e) {
437             // Shouldn't happen.
438         }
439         return false;
440     }
441 
isPackageAvailable()442     private boolean isPackageAvailable() {
443         String packageName = mIntent.getComponent().getPackageName();
444         try {
445             mPackageManagerAdapter.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier());
446             return true;
447         } catch (PackageManager.NameNotFoundException e) {
448             if (DEBUG) {
449                 Log.d(TAG, "Package not available: " + packageName, e);
450             } else {
451                 Log.d(TAG, "Package not available: " + packageName);
452             }
453         }
454         return false;
455     }
456 
queueMessage(int message)457     private void queueMessage(int message) {
458         synchronized (mQueuedMessages) {
459             mQueuedMessages.add(message);
460         }
461     }
462 
463     @Override
onTileAdded()464     public void onTileAdded() {
465         if (DEBUG) Log.d(TAG, "onTileAdded");
466         if (mWrapper == null || !mWrapper.onTileAdded()) {
467             queueMessage(MSG_ON_ADDED);
468             handleDeath();
469         }
470     }
471 
472     @Override
onTileRemoved()473     public void onTileRemoved() {
474         if (DEBUG) Log.d(TAG, "onTileRemoved");
475         if (mWrapper == null || !mWrapper.onTileRemoved()) {
476             queueMessage(MSG_ON_REMOVED);
477             handleDeath();
478         }
479     }
480 
481     @Override
onStartListening()482     public void onStartListening() {
483         if (DEBUG) Log.d(TAG, "onStartListening");
484         mListening = true;
485         if (mWrapper != null && !mWrapper.onStartListening()) {
486             handleDeath();
487         }
488     }
489 
490     @Override
onStopListening()491     public void onStopListening() {
492         if (DEBUG) Log.d(TAG, "onStopListening");
493         mListening = false;
494         if (mWrapper != null && !mWrapper.onStopListening()) {
495             handleDeath();
496         }
497     }
498 
499     @Override
onClick(IBinder iBinder)500     public void onClick(IBinder iBinder) {
501         if (DEBUG) Log.d(TAG, "onClick " + iBinder + " " + mUser);
502         if (mWrapper == null || !mWrapper.onClick(iBinder)) {
503             mClickBinder = iBinder;
504             queueMessage(MSG_ON_CLICK);
505             handleDeath();
506         }
507     }
508 
509     @Override
onUnlockComplete()510     public void onUnlockComplete() {
511         if (DEBUG) Log.d(TAG, "onUnlockComplete");
512         if (mWrapper == null || !mWrapper.onUnlockComplete()) {
513             queueMessage(MSG_ON_UNLOCK_COMPLETE);
514             handleDeath();
515         }
516     }
517 
518     @Nullable
519     @Override
asBinder()520     public IBinder asBinder() {
521         return mWrapper != null ? mWrapper.asBinder() : null;
522     }
523 
524     @Override
binderDied()525     public void binderDied() {
526         if (DEBUG) Log.d(TAG, "binderDeath");
527         handleDeath();
528     }
529 
getToken()530     public IBinder getToken() {
531         return mToken;
532     }
533 
freeWrapper()534     private void freeWrapper() {
535         if (mWrapper != null) {
536             try {
537                 mWrapper.asBinder().unlinkToDeath(this, 0);
538             } catch (NoSuchElementException e) {
539                 Log.w(TAG, "Trying to unlink not linked recipient for component"
540                         + mIntent.getComponent().flattenToShortString());
541             }
542             mWrapper = null;
543         }
544     }
545 
546     public interface TileChangeListener {
onTileChanged(ComponentName tile)547         void onTileChanged(ComponentName tile);
548     }
549 }
550