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