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