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
163                                 | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
164                                 | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
165                                 | Context.BIND_WAIVE_PRIORITY,
166                         mUser);
167             } catch (SecurityException e) {
168                 Log.e(TAG, "Failed to bind to service", e);
169                 mIsBound = false;
170             }
171         } else {
172             if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser);
173             // Give it another chance next time it needs to be bound, out of kindness.
174             mBindTryCount = 0;
175             mWrapper = null;
176             if (mIsBound) {
177                 mContext.unbindService(this);
178                 mIsBound = false;
179             }
180         }
181     }
182 
183     @Override
onServiceConnected(ComponentName name, IBinder service)184     public void onServiceConnected(ComponentName name, IBinder service) {
185         if (DEBUG) Log.d(TAG, "onServiceConnected " + name);
186         // Got a connection, set the binding count to 0.
187         mBindTryCount = 0;
188         final QSTileServiceWrapper wrapper = new QSTileServiceWrapper(Stub.asInterface(service));
189         try {
190             service.linkToDeath(this, 0);
191         } catch (RemoteException e) {
192         }
193         mWrapper = wrapper;
194         handlePendingMessages();
195     }
196 
197     @Override
onServiceDisconnected(ComponentName name)198     public void onServiceDisconnected(ComponentName name) {
199         if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name);
200         handleDeath();
201     }
202 
handlePendingMessages()203     private void handlePendingMessages() {
204         // This ordering is laid out manually to make sure we preserve the TileService
205         // lifecycle.
206         ArraySet<Integer> queue;
207         synchronized (mQueuedMessages) {
208             queue = new ArraySet<>(mQueuedMessages);
209             mQueuedMessages.clear();
210         }
211         if (queue.contains(MSG_ON_ADDED)) {
212             if (DEBUG) Log.d(TAG, "Handling pending onAdded");
213             onTileAdded();
214         }
215         if (mListening) {
216             if (DEBUG) Log.d(TAG, "Handling pending onStartListening");
217             onStartListening();
218         }
219         if (queue.contains(MSG_ON_CLICK)) {
220             if (DEBUG) Log.d(TAG, "Handling pending onClick");
221             if (!mListening) {
222                 Log.w(TAG, "Managed to get click on non-listening state...");
223                 // Skipping click since lost click privileges.
224             } else {
225                 onClick(mClickBinder);
226             }
227         }
228         if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) {
229             if (DEBUG) Log.d(TAG, "Handling pending onUnlockComplete");
230             if (!mListening) {
231                 Log.w(TAG, "Managed to get unlock on non-listening state...");
232                 // Skipping unlock since lost click privileges.
233             } else {
234                 onUnlockComplete();
235             }
236         }
237         if (queue.contains(MSG_ON_REMOVED)) {
238             if (DEBUG) Log.d(TAG, "Handling pending onRemoved");
239             if (mListening) {
240                 Log.w(TAG, "Managed to get remove in listening state...");
241                 onStopListening();
242             }
243             onTileRemoved();
244         }
245         if (mUnbindImmediate) {
246             mUnbindImmediate = false;
247             setBindService(false);
248         }
249     }
250 
handleDestroy()251     public void handleDestroy() {
252         if (DEBUG) Log.d(TAG, "handleDestroy");
253         if (mReceiverRegistered) {
254             stopPackageListening();
255         }
256     }
257 
handleDeath()258     private void handleDeath() {
259         if (mWrapper == null) return;
260         mWrapper = null;
261         if (!mBound) return;
262         if (DEBUG) Log.d(TAG, "handleDeath");
263         if (checkComponentState()) {
264             mHandler.postDelayed(new Runnable() {
265                 @Override
266                 public void run() {
267                     if (mBound) {
268                         // Retry binding.
269                         setBindService(true);
270                     }
271                 }
272             }, mBindRetryDelay);
273         }
274     }
275 
checkComponentState()276     private boolean checkComponentState() {
277         if (!isPackageAvailable() || !isComponentAvailable()) {
278             startPackageListening();
279             return false;
280         }
281         return true;
282     }
283 
startPackageListening()284     private void startPackageListening() {
285         if (DEBUG) Log.d(TAG, "startPackageListening");
286         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
287         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
288         filter.addDataScheme("package");
289         mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler);
290         filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
291         mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler);
292         mReceiverRegistered = true;
293     }
294 
stopPackageListening()295     private void stopPackageListening() {
296         if (DEBUG) Log.d(TAG, "stopPackageListening");
297         mContext.unregisterReceiver(this);
298         mReceiverRegistered = false;
299     }
300 
setTileChangeListener(TileChangeListener changeListener)301     public void setTileChangeListener(TileChangeListener changeListener) {
302         mChangeListener = changeListener;
303     }
304 
305     @Override
onReceive(Context context, Intent intent)306     public void onReceive(Context context, Intent intent) {
307         if (DEBUG) Log.d(TAG, "onReceive: " + intent);
308         if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
309             Uri data = intent.getData();
310             String pkgName = data.getEncodedSchemeSpecificPart();
311             if (!Objects.equals(pkgName, mIntent.getComponent().getPackageName())) {
312                 return;
313             }
314         }
315         if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) && mChangeListener != null) {
316             mChangeListener.onTileChanged(mIntent.getComponent());
317         }
318         stopPackageListening();
319         if (mBound) {
320             // Trying to bind again will check the state of the package before bothering to bind.
321             if (DEBUG) Log.d(TAG, "Trying to rebind");
322             setBindService(true);
323         }
324     }
325 
isComponentAvailable()326     private boolean isComponentAvailable() {
327         String packageName = mIntent.getComponent().getPackageName();
328         try {
329             ServiceInfo si = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
330                     0, mUser.getIdentifier());
331             if (DEBUG && si == null) Log.d(TAG, "Can't find component " + mIntent.getComponent());
332             return si != null;
333         } catch (RemoteException e) {
334             // Shouldn't happen.
335         }
336         return false;
337     }
338 
isPackageAvailable()339     private boolean isPackageAvailable() {
340         String packageName = mIntent.getComponent().getPackageName();
341         try {
342             mPackageManagerAdapter.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier());
343             return true;
344         } catch (PackageManager.NameNotFoundException e) {
345             if (DEBUG) Log.d(TAG, "Package not available: " + packageName, e);
346             else Log.d(TAG, "Package not available: " + packageName);
347         }
348         return false;
349     }
350 
queueMessage(int message)351     private void queueMessage(int message) {
352         synchronized (mQueuedMessages) {
353             mQueuedMessages.add(message);
354         }
355     }
356 
357     @Override
onTileAdded()358     public void onTileAdded() {
359         if (DEBUG) Log.d(TAG, "onTileAdded");
360         if (mWrapper == null || !mWrapper.onTileAdded()) {
361             queueMessage(MSG_ON_ADDED);
362             handleDeath();
363         }
364     }
365 
366     @Override
onTileRemoved()367     public void onTileRemoved() {
368         if (DEBUG) Log.d(TAG, "onTileRemoved");
369         if (mWrapper == null || !mWrapper.onTileRemoved()) {
370             queueMessage(MSG_ON_REMOVED);
371             handleDeath();
372         }
373     }
374 
375     @Override
onStartListening()376     public void onStartListening() {
377         if (DEBUG) Log.d(TAG, "onStartListening");
378         mListening = true;
379         if (mWrapper != null && !mWrapper.onStartListening()) {
380             handleDeath();
381         }
382     }
383 
384     @Override
onStopListening()385     public void onStopListening() {
386         if (DEBUG) Log.d(TAG, "onStopListening");
387         mListening = false;
388         if (mWrapper != null && !mWrapper.onStopListening()) {
389             handleDeath();
390         }
391     }
392 
393     @Override
onClick(IBinder iBinder)394     public void onClick(IBinder iBinder) {
395         if (DEBUG) Log.d(TAG, "onClick " + iBinder + " " + mUser);
396         if (mWrapper == null || !mWrapper.onClick(iBinder)) {
397             mClickBinder = iBinder;
398             queueMessage(MSG_ON_CLICK);
399             handleDeath();
400         }
401     }
402 
403     @Override
onUnlockComplete()404     public void onUnlockComplete() {
405         if (DEBUG) Log.d(TAG, "onUnlockComplete");
406         if (mWrapper == null || !mWrapper.onUnlockComplete()) {
407             queueMessage(MSG_ON_UNLOCK_COMPLETE);
408             handleDeath();
409         }
410     }
411 
412     @Override
asBinder()413     public IBinder asBinder() {
414         return mWrapper != null ? mWrapper.asBinder() : null;
415     }
416 
417     @Override
binderDied()418     public void binderDied() {
419         if (DEBUG) Log.d(TAG, "binderDeath");
420         handleDeath();
421     }
422 
getToken()423     public IBinder getToken() {
424         return mToken;
425     }
426 
427     public interface TileChangeListener {
onTileChanged(ComponentName tile)428         void onTileChanged(ComponentName tile);
429     }
430 
isTileAdded(Context context, ComponentName component)431     public static boolean isTileAdded(Context context, ComponentName component) {
432         return context.getSharedPreferences(TILES, 0).getBoolean(component.flattenToString(), false);
433     }
434 
setTileAdded(Context context, ComponentName component, boolean added)435     public static void setTileAdded(Context context, ComponentName component, boolean added) {
436         context.getSharedPreferences(TILES, 0).edit().putBoolean(component.flattenToString(),
437                 added).commit();
438     }
439 }
440