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