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