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