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