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