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