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