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