blob: 1170d7b6e8a94e2cc3928c98cd0cab9874e4732a [file] [log] [blame]
Jason Monkd5a204f2015-12-21 08:50:01 -05001/*
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;
Jason Monkd5a204f2015-12-21 08:50:01 -050024import android.content.pm.PackageManager;
25import android.content.pm.ServiceInfo;
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -040026import android.content.ServiceConnection;
Jason Monkd5a204f2015-12-21 08:50:01 -050027import android.net.Uri;
Jason Monkee68fd82016-06-23 13:12:23 -040028import android.os.Binder;
Jason Monkd5a204f2015-12-21 08:50:01 -050029import android.os.Handler;
30import android.os.IBinder;
31import android.os.RemoteException;
32import android.os.UserHandle;
Jason Monkfe8f6822015-12-21 15:12:01 -050033import android.service.quicksettings.IQSService;
Jason Monkd5a204f2015-12-21 08:50:01 -050034import android.service.quicksettings.IQSTileService;
35import android.service.quicksettings.Tile;
Jason Monk97d22722016-04-07 11:41:47 -040036import android.service.quicksettings.TileService;
Jason Monkd5a204f2015-12-21 08:50:01 -050037import android.support.annotation.VisibleForTesting;
38import android.util.ArraySet;
39import android.util.Log;
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -040040import com.android.systemui.qs.external.PackageManagerAdapter;
Jason Monkee68fd82016-06-23 13:12:23 -040041
Narayan Kamath607223f2018-02-19 14:09:02 +000042import java.util.Objects;
Jason Monkd5a204f2015-12-21 08:50:01 -050043import java.util.Set;
44
45/**
46 * Manages the lifecycle of a TileService.
47 * <p>
48 * Will keep track of all calls on the IQSTileService interface and will relay those calls to the
49 * TileService as soon as it is bound. It will only bind to the service when it is allowed to
50 * ({@link #setBindService(boolean)}) and when the service is available.
51 */
52public class TileLifecycleManager extends BroadcastReceiver implements
53 IQSTileService, ServiceConnection, IBinder.DeathRecipient {
54 public static final boolean DEBUG = false;
55
56 private static final String TAG = "TileLifecycleManager";
57
58 private static final int MSG_ON_ADDED = 0;
59 private static final int MSG_ON_REMOVED = 1;
60 private static final int MSG_ON_CLICK = 2;
Jason Monk94295132016-01-12 11:27:02 -050061 private static final int MSG_ON_UNLOCK_COMPLETE = 3;
Jason Monkd5a204f2015-12-21 08:50:01 -050062
63 // Bind retry control.
64 private static final int MAX_BIND_RETRIES = 5;
Geoffrey Pitschebee1a32016-09-09 13:04:12 -040065 private static final int DEFAULT_BIND_RETRY_DELAY = 1000;
Jason Monkd5a204f2015-12-21 08:50:01 -050066
Jason Monkbaade752016-08-25 15:57:14 -040067 // Shared prefs that hold tile lifecycle info.
68 private static final String TILES = "tiles_prefs";
69
Jason Monkd5a204f2015-12-21 08:50:01 -050070 private final Context mContext;
71 private final Handler mHandler;
72 private final Intent mIntent;
73 private final UserHandle mUser;
Jason Monkee68fd82016-06-23 13:12:23 -040074 private final IBinder mToken = new Binder();
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -040075 private final PackageManagerAdapter mPackageManagerAdapter;
Jason Monkd5a204f2015-12-21 08:50:01 -050076
77 private Set<Integer> mQueuedMessages = new ArraySet<>();
78 private QSTileServiceWrapper mWrapper;
79 private boolean mListening;
Jason Monkd5a204f2015-12-21 08:50:01 -050080 private IBinder mClickBinder;
81
82 private int mBindTryCount;
Geoffrey Pitschebee1a32016-09-09 13:04:12 -040083 private int mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY;
Jason Monkd5a204f2015-12-21 08:50:01 -050084 private boolean mBound;
Jason Monkd5a204f2015-12-21 08:50:01 -050085 boolean mReceiverRegistered;
Jason Monk51c444b2016-01-06 16:32:29 -050086 private boolean mUnbindImmediate;
Jason Monk624cbe22016-05-02 10:42:17 -040087 private TileChangeListener mChangeListener;
Jason Monk6d21e0d2016-05-25 11:37:47 -040088 // Return value from bindServiceAsUser, determines whether safe to call unbind.
89 private boolean mIsBound;
Jason Monkd5a204f2015-12-21 08:50:01 -050090
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -040091 public TileLifecycleManager(Handler handler, Context context, IQSService service, Tile tile,
92 Intent intent, UserHandle user) {
93 this(handler, context, service, tile, intent, user, new PackageManagerAdapter(context));
94 }
95
96 @VisibleForTesting
97 TileLifecycleManager(Handler handler, Context context, IQSService service, Tile tile,
98 Intent intent, UserHandle user, PackageManagerAdapter packageManagerAdapter) {
Jason Monkd5a204f2015-12-21 08:50:01 -050099 mContext = context;
100 mHandler = handler;
101 mIntent = intent;
Jason Monka3453b8b2016-06-17 12:42:59 -0400102 mIntent.putExtra(TileService.EXTRA_SERVICE, service.asBinder());
Jason Monkee68fd82016-06-23 13:12:23 -0400103 mIntent.putExtra(TileService.EXTRA_TOKEN, mToken);
Jason Monkd5a204f2015-12-21 08:50:01 -0500104 mUser = user;
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400105 mPackageManagerAdapter = packageManagerAdapter;
Jason Monk1ffa11b2016-03-08 14:44:23 -0500106 if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser);
Jason Monkd5a204f2015-12-21 08:50:01 -0500107 }
108
Jason Monkfe8f6822015-12-21 15:12:01 -0500109 public ComponentName getComponent() {
110 return mIntent.getComponent();
111 }
112
Jason Monkd5a204f2015-12-21 08:50:01 -0500113 public boolean hasPendingClick() {
114 synchronized (mQueuedMessages) {
115 return mQueuedMessages.contains(MSG_ON_CLICK);
116 }
117 }
118
Geoffrey Pitschebee1a32016-09-09 13:04:12 -0400119 public void setBindRetryDelay(int delayMs) {
120 mBindRetryDelay = delayMs;
121 }
122
Jason Monk97d22722016-04-07 11:41:47 -0400123 public boolean isActiveTile() {
124 try {
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400125 ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
Jason Monk97d22722016-04-07 11:41:47 -0400126 PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
127 return info.metaData != null
128 && info.metaData.getBoolean(TileService.META_DATA_ACTIVE_TILE, false);
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400129 } catch (PackageManager.NameNotFoundException e) {
Jason Monk97d22722016-04-07 11:41:47 -0400130 return false;
131 }
132 }
133
Jason Monk51c444b2016-01-06 16:32:29 -0500134 /**
135 * Binds just long enough to send any queued messages, then unbinds.
136 */
137 public void flushMessagesAndUnbind() {
138 mUnbindImmediate = true;
139 setBindService(true);
140 }
141
Jason Monkd5a204f2015-12-21 08:50:01 -0500142 public void setBindService(boolean bind) {
Jason Monk34f6cbc2016-08-30 16:33:48 -0400143 if (mBound && mUnbindImmediate) {
144 // If we are already bound and expecting to unbind, this means we should stay bound
145 // because something else wants to hold the connection open.
146 mUnbindImmediate = false;
147 return;
148 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500149 mBound = bind;
150 if (bind) {
151 if (mBindTryCount == MAX_BIND_RETRIES) {
152 // Too many failures, give up on this tile until an update.
153 startPackageListening();
154 return;
155 }
156 if (!checkComponentState()) {
157 return;
158 }
Jason Monk1ffa11b2016-03-08 14:44:23 -0500159 if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser);
Jason Monkd5a204f2015-12-21 08:50:01 -0500160 mBindTryCount++;
Will Harmon604c2f92016-06-06 12:54:59 -0700161 try {
162 mIsBound = mContext.bindServiceAsUser(mIntent, this,
163 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
164 mUser);
165 } catch (SecurityException e) {
166 Log.e(TAG, "Failed to bind to service", e);
167 mIsBound = false;
168 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500169 } else {
Jason Monk1ffa11b2016-03-08 14:44:23 -0500170 if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser);
Jason Monkd5a204f2015-12-21 08:50:01 -0500171 // Give it another chance next time it needs to be bound, out of kindness.
172 mBindTryCount = 0;
Jason Monkfe8f6822015-12-21 15:12:01 -0500173 mWrapper = null;
Jason Monk6d21e0d2016-05-25 11:37:47 -0400174 if (mIsBound) {
175 mContext.unbindService(this);
176 mIsBound = false;
177 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500178 }
179 }
180
181 @Override
182 public void onServiceConnected(ComponentName name, IBinder service) {
183 if (DEBUG) Log.d(TAG, "onServiceConnected " + name);
184 // Got a connection, set the binding count to 0.
185 mBindTryCount = 0;
Jason Monk29c93ce2016-04-20 11:41:36 -0400186 final QSTileServiceWrapper wrapper = new QSTileServiceWrapper(Stub.asInterface(service));
Jason Monkd5a204f2015-12-21 08:50:01 -0500187 try {
188 service.linkToDeath(this, 0);
189 } catch (RemoteException e) {
190 }
Jason Monk29c93ce2016-04-20 11:41:36 -0400191 mWrapper = wrapper;
Jason Monkd5a204f2015-12-21 08:50:01 -0500192 handlePendingMessages();
193 }
194
195 @Override
196 public void onServiceDisconnected(ComponentName name) {
197 if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name);
Jason Monk624cbe22016-05-02 10:42:17 -0400198 handleDeath();
Jason Monkd5a204f2015-12-21 08:50:01 -0500199 }
200
201 private void handlePendingMessages() {
202 // This ordering is laid out manually to make sure we preserve the TileService
203 // lifecycle.
204 ArraySet<Integer> queue;
205 synchronized (mQueuedMessages) {
206 queue = new ArraySet<>(mQueuedMessages);
207 mQueuedMessages.clear();
208 }
209 if (queue.contains(MSG_ON_ADDED)) {
210 if (DEBUG) Log.d(TAG, "Handling pending onAdded");
211 onTileAdded();
212 }
213 if (mListening) {
214 if (DEBUG) Log.d(TAG, "Handling pending onStartListening");
Jason Monkd5a204f2015-12-21 08:50:01 -0500215 onStartListening();
216 }
217 if (queue.contains(MSG_ON_CLICK)) {
218 if (DEBUG) Log.d(TAG, "Handling pending onClick");
219 if (!mListening) {
220 Log.w(TAG, "Managed to get click on non-listening state...");
221 // Skipping click since lost click privileges.
222 } else {
223 onClick(mClickBinder);
224 }
225 }
Jason Monk94295132016-01-12 11:27:02 -0500226 if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) {
227 if (DEBUG) Log.d(TAG, "Handling pending onUnlockComplete");
228 if (!mListening) {
229 Log.w(TAG, "Managed to get unlock on non-listening state...");
230 // Skipping unlock since lost click privileges.
231 } else {
232 onUnlockComplete();
233 }
234 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500235 if (queue.contains(MSG_ON_REMOVED)) {
236 if (DEBUG) Log.d(TAG, "Handling pending onRemoved");
237 if (mListening) {
238 Log.w(TAG, "Managed to get remove in listening state...");
239 onStopListening();
240 }
241 onTileRemoved();
242 }
Jason Monk51c444b2016-01-06 16:32:29 -0500243 if (mUnbindImmediate) {
244 mUnbindImmediate = false;
245 setBindService(false);
246 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500247 }
248
249 public void handleDestroy() {
250 if (DEBUG) Log.d(TAG, "handleDestroy");
251 if (mReceiverRegistered) {
252 stopPackageListening();
253 }
254 }
255
256 private void handleDeath() {
257 if (mWrapper == null) return;
258 mWrapper = null;
259 if (!mBound) return;
260 if (DEBUG) Log.d(TAG, "handleDeath");
261 if (checkComponentState()) {
262 mHandler.postDelayed(new Runnable() {
263 @Override
264 public void run() {
265 if (mBound) {
266 // Retry binding.
267 setBindService(true);
268 }
269 }
Geoffrey Pitschebee1a32016-09-09 13:04:12 -0400270 }, mBindRetryDelay);
Jason Monkd5a204f2015-12-21 08:50:01 -0500271 }
272 }
273
Jason Monkd5a204f2015-12-21 08:50:01 -0500274 private boolean checkComponentState() {
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400275 if (!isPackageAvailable() || !isComponentAvailable()) {
Jason Monkd5a204f2015-12-21 08:50:01 -0500276 startPackageListening();
277 return false;
278 }
279 return true;
280 }
281
282 private void startPackageListening() {
283 if (DEBUG) Log.d(TAG, "startPackageListening");
284 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
285 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
286 filter.addDataScheme("package");
287 mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler);
Jason Monk1c2fea82016-03-11 11:33:36 -0500288 filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
289 mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler);
Jason Monkd5a204f2015-12-21 08:50:01 -0500290 mReceiverRegistered = true;
291 }
292
293 private void stopPackageListening() {
294 if (DEBUG) Log.d(TAG, "stopPackageListening");
295 mContext.unregisterReceiver(this);
296 mReceiverRegistered = false;
297 }
298
Jason Monk624cbe22016-05-02 10:42:17 -0400299 public void setTileChangeListener(TileChangeListener changeListener) {
300 mChangeListener = changeListener;
301 }
302
Jason Monkd5a204f2015-12-21 08:50:01 -0500303 @Override
304 public void onReceive(Context context, Intent intent) {
305 if (DEBUG) Log.d(TAG, "onReceive: " + intent);
Jason Monk1c2fea82016-03-11 11:33:36 -0500306 if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
307 Uri data = intent.getData();
308 String pkgName = data.getEncodedSchemeSpecificPart();
Narayan Kamath607223f2018-02-19 14:09:02 +0000309 if (!Objects.equals(pkgName, mIntent.getComponent().getPackageName())) {
Jason Monk1c2fea82016-03-11 11:33:36 -0500310 return;
311 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500312 }
Jason Monk624cbe22016-05-02 10:42:17 -0400313 if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) && mChangeListener != null) {
314 mChangeListener.onTileChanged(mIntent.getComponent());
315 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500316 stopPackageListening();
317 if (mBound) {
318 // Trying to bind again will check the state of the package before bothering to bind.
319 if (DEBUG) Log.d(TAG, "Trying to rebind");
320 setBindService(true);
321 }
322 }
323
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400324 private boolean isComponentAvailable() {
Jason Monkd5a204f2015-12-21 08:50:01 -0500325 String packageName = mIntent.getComponent().getPackageName();
326 try {
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400327 ServiceInfo si = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
Jason Monkd5a204f2015-12-21 08:50:01 -0500328 0, mUser.getIdentifier());
329 if (DEBUG && si == null) Log.d(TAG, "Can't find component " + mIntent.getComponent());
330 return si != null;
331 } catch (RemoteException e) {
332 // Shouldn't happen.
333 }
334 return false;
335 }
336
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400337 private boolean isPackageAvailable() {
Jason Monkd5a204f2015-12-21 08:50:01 -0500338 String packageName = mIntent.getComponent().getPackageName();
339 try {
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400340 mPackageManagerAdapter.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier());
Jason Monkd5a204f2015-12-21 08:50:01 -0500341 return true;
342 } catch (PackageManager.NameNotFoundException e) {
343 if (DEBUG) Log.d(TAG, "Package not available: " + packageName, e);
344 else Log.d(TAG, "Package not available: " + packageName);
345 }
346 return false;
347 }
348
349 private void queueMessage(int message) {
350 synchronized (mQueuedMessages) {
351 mQueuedMessages.add(message);
352 }
353 }
354
355 @Override
Jason Monkd5a204f2015-12-21 08:50:01 -0500356 public void onTileAdded() {
357 if (DEBUG) Log.d(TAG, "onTileAdded");
358 if (mWrapper == null || !mWrapper.onTileAdded()) {
359 queueMessage(MSG_ON_ADDED);
360 handleDeath();
361 }
362 }
363
364 @Override
365 public void onTileRemoved() {
366 if (DEBUG) Log.d(TAG, "onTileRemoved");
367 if (mWrapper == null || !mWrapper.onTileRemoved()) {
368 queueMessage(MSG_ON_REMOVED);
369 handleDeath();
370 }
371 }
372
373 @Override
374 public void onStartListening() {
375 if (DEBUG) Log.d(TAG, "onStartListening");
376 mListening = true;
377 if (mWrapper != null && !mWrapper.onStartListening()) {
378 handleDeath();
379 }
380 }
381
382 @Override
383 public void onStopListening() {
384 if (DEBUG) Log.d(TAG, "onStopListening");
385 mListening = false;
386 if (mWrapper != null && !mWrapper.onStopListening()) {
387 handleDeath();
388 }
389 }
390
391 @Override
392 public void onClick(IBinder iBinder) {
Jason Monk1ffa11b2016-03-08 14:44:23 -0500393 if (DEBUG) Log.d(TAG, "onClick " + iBinder + " " + mUser);
Jason Monkd5a204f2015-12-21 08:50:01 -0500394 if (mWrapper == null || !mWrapper.onClick(iBinder)) {
395 mClickBinder = iBinder;
396 queueMessage(MSG_ON_CLICK);
397 handleDeath();
398 }
399 }
400
401 @Override
Jason Monk94295132016-01-12 11:27:02 -0500402 public void onUnlockComplete() {
403 if (DEBUG) Log.d(TAG, "onUnlockComplete");
404 if (mWrapper == null || !mWrapper.onUnlockComplete()) {
405 queueMessage(MSG_ON_UNLOCK_COMPLETE);
406 handleDeath();
407 }
408 }
409
410 @Override
Jason Monkd5a204f2015-12-21 08:50:01 -0500411 public IBinder asBinder() {
412 return mWrapper != null ? mWrapper.asBinder() : null;
413 }
414
415 @Override
416 public void binderDied() {
417 if (DEBUG) Log.d(TAG, "binderDeath");
418 handleDeath();
419 }
Jason Monk624cbe22016-05-02 10:42:17 -0400420
Jason Monkee68fd82016-06-23 13:12:23 -0400421 public IBinder getToken() {
422 return mToken;
423 }
424
Jason Monk624cbe22016-05-02 10:42:17 -0400425 public interface TileChangeListener {
426 void onTileChanged(ComponentName tile);
427 }
Jason Monkbaade752016-08-25 15:57:14 -0400428
429 public static boolean isTileAdded(Context context, ComponentName component) {
430 return context.getSharedPreferences(TILES, 0).getBoolean(component.flattenToString(), false);
431 }
432
433 public static void setTileAdded(Context context, ComponentName component, boolean added) {
434 context.getSharedPreferences(TILES, 0).edit().putBoolean(component.flattenToString(),
435 added).commit();
436 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500437}