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