TileLifecycleManager.java revision 604c2f9440743f72f136505b2b6afe6f2aed9072
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            try {
138                mIsBound = mContext.bindServiceAsUser(mIntent, this,
139                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
140                        mUser);
141            } catch (SecurityException e) {
142                Log.e(TAG, "Failed to bind to service", e);
143                mIsBound = false;
144            }
145        } else {
146            if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser);
147            // Give it another chance next time it needs to be bound, out of kindness.
148            mBindTryCount = 0;
149            mWrapper = null;
150            if (mIsBound) {
151                mContext.unbindService(this);
152                mIsBound = false;
153            }
154        }
155    }
156
157    @Override
158    public void onServiceConnected(ComponentName name, IBinder service) {
159        if (DEBUG) Log.d(TAG, "onServiceConnected " + name);
160        // Got a connection, set the binding count to 0.
161        mBindTryCount = 0;
162        final QSTileServiceWrapper wrapper = new QSTileServiceWrapper(Stub.asInterface(service));
163        try {
164            service.linkToDeath(this, 0);
165        } catch (RemoteException e) {
166        }
167        if (!wrapper.setQSService(mService)) {
168            handleDeath();
169            return;
170        }
171        if (!wrapper.setQSTile(mTile)) {
172            handleDeath();
173            return;
174        }
175        mWrapper = wrapper;
176        handlePendingMessages();
177    }
178
179    @Override
180    public void onServiceDisconnected(ComponentName name) {
181        if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name);
182        handleDeath();
183    }
184
185    private void handlePendingMessages() {
186        // This ordering is laid out manually to make sure we preserve the TileService
187        // lifecycle.
188        ArraySet<Integer> queue;
189        synchronized (mQueuedMessages) {
190            queue = new ArraySet<>(mQueuedMessages);
191            mQueuedMessages.clear();
192        }
193        if (queue.contains(MSG_ON_ADDED)) {
194            if (DEBUG) Log.d(TAG, "Handling pending onAdded");
195            onTileAdded();
196        }
197        if (mListening) {
198            if (DEBUG) Log.d(TAG, "Handling pending onStartListening");
199            onStartListening();
200        }
201        if (queue.contains(MSG_ON_CLICK)) {
202            if (DEBUG) Log.d(TAG, "Handling pending onClick");
203            if (!mListening) {
204                Log.w(TAG, "Managed to get click on non-listening state...");
205                // Skipping click since lost click privileges.
206            } else {
207                onClick(mClickBinder);
208            }
209        }
210        if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) {
211            if (DEBUG) Log.d(TAG, "Handling pending onUnlockComplete");
212            if (!mListening) {
213                Log.w(TAG, "Managed to get unlock on non-listening state...");
214                // Skipping unlock since lost click privileges.
215            } else {
216                onUnlockComplete();
217            }
218        }
219        if (queue.contains(MSG_ON_REMOVED)) {
220            if (DEBUG) Log.d(TAG, "Handling pending onRemoved");
221            if (mListening) {
222                Log.w(TAG, "Managed to get remove in listening state...");
223                onStopListening();
224            }
225            onTileRemoved();
226        }
227        if (mUnbindImmediate) {
228            mUnbindImmediate = false;
229            setBindService(false);
230        }
231    }
232
233    public void handleDestroy() {
234        if (DEBUG) Log.d(TAG, "handleDestroy");
235        if (mReceiverRegistered) {
236            stopPackageListening();
237        }
238    }
239
240    private void handleDeath() {
241        if (mWrapper == null) return;
242        mWrapper = null;
243        if (!mBound) return;
244        if (DEBUG) Log.d(TAG, "handleDeath");
245        if (checkComponentState()) {
246            mHandler.postDelayed(new Runnable() {
247                @Override
248                public void run() {
249                    if (mBound) {
250                        // Retry binding.
251                        setBindService(true);
252                    }
253                }
254            }, BIND_RETRY_DELAY);
255        }
256    }
257
258    @Override
259    public void setQSTile(Tile tile) {
260        if (DEBUG) Log.d(TAG, "setQSTile " + tile);
261        mTile = tile;
262        if (mWrapper != null && !mWrapper.setQSTile(tile)) {
263            handleDeath();
264        }
265    }
266
267    private boolean checkComponentState() {
268        PackageManager pm = mContext.getPackageManager();
269        if (!isPackageAvailable(pm) || !isComponentAvailable(pm)) {
270            startPackageListening();
271            return false;
272        }
273        return true;
274    }
275
276    private void startPackageListening() {
277        if (DEBUG) Log.d(TAG, "startPackageListening");
278        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
279        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
280        filter.addDataScheme("package");
281        mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler);
282        filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
283        mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler);
284        mReceiverRegistered = true;
285    }
286
287    private void stopPackageListening() {
288        if (DEBUG) Log.d(TAG, "stopPackageListening");
289        mContext.unregisterReceiver(this);
290        mReceiverRegistered = false;
291    }
292
293    public void setTileChangeListener(TileChangeListener changeListener) {
294        mChangeListener = changeListener;
295    }
296
297    @Override
298    public void onReceive(Context context, Intent intent) {
299        if (DEBUG) Log.d(TAG, "onReceive: " + intent);
300        if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
301            Uri data = intent.getData();
302            String pkgName = data.getEncodedSchemeSpecificPart();
303            if (!Objects.equal(pkgName, mIntent.getComponent().getPackageName())) {
304                return;
305            }
306        }
307        if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) && mChangeListener != null) {
308            mChangeListener.onTileChanged(mIntent.getComponent());
309        }
310        stopPackageListening();
311        if (mBound) {
312            // Trying to bind again will check the state of the package before bothering to bind.
313            if (DEBUG) Log.d(TAG, "Trying to rebind");
314            setBindService(true);
315        }
316    }
317
318    private boolean isComponentAvailable(PackageManager pm) {
319        String packageName = mIntent.getComponent().getPackageName();
320        try {
321            ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(mIntent.getComponent(),
322                    0, mUser.getIdentifier());
323            if (DEBUG && si == null) Log.d(TAG, "Can't find component " + mIntent.getComponent());
324            return si != null;
325        } catch (RemoteException e) {
326            // Shouldn't happen.
327        }
328        return false;
329    }
330
331    private boolean isPackageAvailable(PackageManager pm) {
332        String packageName = mIntent.getComponent().getPackageName();
333        try {
334            pm.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier());
335            return true;
336        } catch (PackageManager.NameNotFoundException e) {
337            if (DEBUG) Log.d(TAG, "Package not available: " + packageName, e);
338            else Log.d(TAG, "Package not available: " + packageName);
339        }
340        return false;
341    }
342
343    private void queueMessage(int message) {
344        synchronized (mQueuedMessages) {
345            mQueuedMessages.add(message);
346        }
347    }
348
349    @Override
350    public void setQSService(IQSService service) {
351        mService = service;
352        if (mWrapper == null || !mWrapper.setQSService(service)) {
353            handleDeath();
354        }
355    }
356
357    @Override
358    public void onTileAdded() {
359        if (DEBUG) Log.d(TAG, "onTileAdded");
360        if (mWrapper == null || !mWrapper.onTileAdded()) {
361            queueMessage(MSG_ON_ADDED);
362            handleDeath();
363        }
364    }
365
366    @Override
367    public void onTileRemoved() {
368        if (DEBUG) Log.d(TAG, "onTileRemoved");
369        if (mWrapper == null || !mWrapper.onTileRemoved()) {
370            queueMessage(MSG_ON_REMOVED);
371            handleDeath();
372        }
373    }
374
375    @Override
376    public void onStartListening() {
377        if (DEBUG) Log.d(TAG, "onStartListening");
378        mListening = true;
379        if (mWrapper != null && !mWrapper.onStartListening()) {
380            handleDeath();
381        }
382    }
383
384    @Override
385    public void onStopListening() {
386        if (DEBUG) Log.d(TAG, "onStopListening");
387        mListening = false;
388        if (mWrapper != null && !mWrapper.onStopListening()) {
389            handleDeath();
390        }
391    }
392
393    @Override
394    public void onClick(IBinder iBinder) {
395        if (DEBUG) Log.d(TAG, "onClick " + iBinder + " " + mUser);
396        if (mWrapper == null || !mWrapper.onClick(iBinder)) {
397            mClickBinder = iBinder;
398            queueMessage(MSG_ON_CLICK);
399            handleDeath();
400        }
401    }
402
403    @Override
404    public void onUnlockComplete() {
405        if (DEBUG) Log.d(TAG, "onUnlockComplete");
406        if (mWrapper == null || !mWrapper.onUnlockComplete()) {
407            queueMessage(MSG_ON_UNLOCK_COMPLETE);
408            handleDeath();
409        }
410    }
411
412    @Override
413    public IBinder asBinder() {
414        return mWrapper != null ? mWrapper.asBinder() : null;
415    }
416
417    @Override
418    public void binderDied() {
419        if (DEBUG) Log.d(TAG, "binderDeath");
420        handleDeath();
421    }
422
423    public interface TileChangeListener {
424        void onTileChanged(ComponentName tile);
425    }
426}
427