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