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