blob: 926141297addec0452fd44ce1d1fe7cb7bb82e6d [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
Jason Monkd5a204f2015-12-21 08:50:01 -050018import android.content.BroadcastReceiver;
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
Gus Prevasab336792018-11-14 13:52:20 -050023import android.content.ServiceConnection;
Jason Monkd5a204f2015-12-21 08:50:01 -050024import android.content.pm.PackageManager;
25import android.content.pm.ServiceInfo;
26import android.net.Uri;
Jason Monkee68fd82016-06-23 13:12:23 -040027import android.os.Binder;
Jason Monkd5a204f2015-12-21 08:50:01 -050028import android.os.Handler;
29import android.os.IBinder;
30import android.os.RemoteException;
31import android.os.UserHandle;
Jason Monkfe8f6822015-12-21 15:12:01 -050032import android.service.quicksettings.IQSService;
Jason Monkd5a204f2015-12-21 08:50:01 -050033import android.service.quicksettings.IQSTileService;
34import android.service.quicksettings.Tile;
Jason Monk97d22722016-04-07 11:41:47 -040035import android.service.quicksettings.TileService;
Jason Monkd5a204f2015-12-21 08:50:01 -050036import android.util.ArraySet;
37import android.util.Log;
Gus Prevasab336792018-11-14 13:52:20 -050038
39import androidx.annotation.VisibleForTesting;
Jason Monkee68fd82016-06-23 13:12:23 -040040
Fabian Kozynski5ca7a512019-10-16 19:56:11 +000041import com.android.systemui.broadcast.BroadcastDispatcher;
42
Narayan Kamath607223f2018-02-19 14:09:02 +000043import java.util.Objects;
Jason Monkd5a204f2015-12-21 08:50:01 -050044import 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;
Jason Monk94295132016-01-12 11:27:02 -050062 private static final int MSG_ON_UNLOCK_COMPLETE = 3;
Jason Monkd5a204f2015-12-21 08:50:01 -050063
64 // Bind retry control.
65 private static final int MAX_BIND_RETRIES = 5;
Geoffrey Pitschebee1a32016-09-09 13:04:12 -040066 private static final int DEFAULT_BIND_RETRY_DELAY = 1000;
Jason Monkd5a204f2015-12-21 08:50:01 -050067
Jason Monkbaade752016-08-25 15:57:14 -040068 // Shared prefs that hold tile lifecycle info.
69 private static final String TILES = "tiles_prefs";
70
Jason Monkd5a204f2015-12-21 08:50:01 -050071 private final Context mContext;
72 private final Handler mHandler;
73 private final Intent mIntent;
74 private final UserHandle mUser;
Jason Monkee68fd82016-06-23 13:12:23 -040075 private final IBinder mToken = new Binder();
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -040076 private final PackageManagerAdapter mPackageManagerAdapter;
Fabian Kozynski5ca7a512019-10-16 19:56:11 +000077 private final BroadcastDispatcher mBroadcastDispatcher;
Jason Monkd5a204f2015-12-21 08:50:01 -050078
79 private Set<Integer> mQueuedMessages = new ArraySet<>();
80 private QSTileServiceWrapper mWrapper;
81 private boolean mListening;
Jason Monkd5a204f2015-12-21 08:50:01 -050082 private IBinder mClickBinder;
83
84 private int mBindTryCount;
Geoffrey Pitschebee1a32016-09-09 13:04:12 -040085 private int mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY;
Jason Monkd5a204f2015-12-21 08:50:01 -050086 private boolean mBound;
Jason Monkd5a204f2015-12-21 08:50:01 -050087 boolean mReceiverRegistered;
Jason Monk51c444b2016-01-06 16:32:29 -050088 private boolean mUnbindImmediate;
Jason Monk624cbe22016-05-02 10:42:17 -040089 private TileChangeListener mChangeListener;
Jason Monk6d21e0d2016-05-25 11:37:47 -040090 // Return value from bindServiceAsUser, determines whether safe to call unbind.
91 private boolean mIsBound;
Jason Monkd5a204f2015-12-21 08:50:01 -050092
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -040093 public TileLifecycleManager(Handler handler, Context context, IQSService service, Tile tile,
Fabian Kozynski5ca7a512019-10-16 19:56:11 +000094 Intent intent, UserHandle user, BroadcastDispatcher broadcastDispatcher) {
95 this(handler, context, service, tile, intent, user, new PackageManagerAdapter(context),
96 broadcastDispatcher);
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -040097 }
98
99 @VisibleForTesting
100 TileLifecycleManager(Handler handler, Context context, IQSService service, Tile tile,
Fabian Kozynski5ca7a512019-10-16 19:56:11 +0000101 Intent intent, UserHandle user, PackageManagerAdapter packageManagerAdapter,
102 BroadcastDispatcher broadcastDispatcher) {
Jason Monkd5a204f2015-12-21 08:50:01 -0500103 mContext = context;
104 mHandler = handler;
105 mIntent = intent;
Jason Monka3453b8b2016-06-17 12:42:59 -0400106 mIntent.putExtra(TileService.EXTRA_SERVICE, service.asBinder());
Jason Monkee68fd82016-06-23 13:12:23 -0400107 mIntent.putExtra(TileService.EXTRA_TOKEN, mToken);
Jason Monkd5a204f2015-12-21 08:50:01 -0500108 mUser = user;
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400109 mPackageManagerAdapter = packageManagerAdapter;
Fabian Kozynski5ca7a512019-10-16 19:56:11 +0000110 mBroadcastDispatcher = broadcastDispatcher;
Jason Monk1ffa11b2016-03-08 14:44:23 -0500111 if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser);
Jason Monkd5a204f2015-12-21 08:50:01 -0500112 }
113
Jason Monkfe8f6822015-12-21 15:12:01 -0500114 public ComponentName getComponent() {
115 return mIntent.getComponent();
116 }
117
Jason Monkd5a204f2015-12-21 08:50:01 -0500118 public boolean hasPendingClick() {
119 synchronized (mQueuedMessages) {
120 return mQueuedMessages.contains(MSG_ON_CLICK);
121 }
122 }
123
Geoffrey Pitschebee1a32016-09-09 13:04:12 -0400124 public void setBindRetryDelay(int delayMs) {
125 mBindRetryDelay = delayMs;
126 }
127
Jason Monk97d22722016-04-07 11:41:47 -0400128 public boolean isActiveTile() {
129 try {
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400130 ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
Jason Monk97d22722016-04-07 11:41:47 -0400131 PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
132 return info.metaData != null
133 && info.metaData.getBoolean(TileService.META_DATA_ACTIVE_TILE, false);
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400134 } catch (PackageManager.NameNotFoundException e) {
Jason Monk97d22722016-04-07 11:41:47 -0400135 return false;
136 }
137 }
138
Jason Monk51c444b2016-01-06 16:32:29 -0500139 /**
Fabian Kozynski05843f02019-06-28 13:19:57 -0400140 * Determines whether the associated TileService is a Boolean Tile.
141 *
142 * @return true if {@link TileService#META_DATA_BOOLEAN_TILE} is set to {@code true} for this
143 * tile
144 * @see TileService#META_DATA_BOOLEAN_TILE
145 */
146 public boolean isBooleanTile() {
147 try {
148 ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
149 PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
150 return info.metaData != null
151 && info.metaData.getBoolean(TileService.META_DATA_BOOLEAN_TILE, false);
152 } catch (PackageManager.NameNotFoundException e) {
153 return false;
154 }
155 }
156
157 /**
Jason Monk51c444b2016-01-06 16:32:29 -0500158 * Binds just long enough to send any queued messages, then unbinds.
159 */
160 public void flushMessagesAndUnbind() {
161 mUnbindImmediate = true;
162 setBindService(true);
163 }
164
Jason Monkd5a204f2015-12-21 08:50:01 -0500165 public void setBindService(boolean bind) {
Jason Monk34f6cbc2016-08-30 16:33:48 -0400166 if (mBound && mUnbindImmediate) {
167 // If we are already bound and expecting to unbind, this means we should stay bound
168 // because something else wants to hold the connection open.
169 mUnbindImmediate = false;
170 return;
171 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500172 mBound = bind;
173 if (bind) {
174 if (mBindTryCount == MAX_BIND_RETRIES) {
175 // Too many failures, give up on this tile until an update.
176 startPackageListening();
177 return;
178 }
179 if (!checkComponentState()) {
180 return;
181 }
Jason Monk1ffa11b2016-03-08 14:44:23 -0500182 if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser);
Jason Monkd5a204f2015-12-21 08:50:01 -0500183 mBindTryCount++;
Will Harmon604c2f92016-06-06 12:54:59 -0700184 try {
185 mIsBound = mContext.bindServiceAsUser(mIntent, this,
Fabian Kozynskiff888da2019-07-01 12:50:03 -0400186 Context.BIND_AUTO_CREATE
187 | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
188 | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
189 | Context.BIND_WAIVE_PRIORITY,
190 mUser);
Will Harmon604c2f92016-06-06 12:54:59 -0700191 } catch (SecurityException e) {
192 Log.e(TAG, "Failed to bind to service", e);
193 mIsBound = false;
194 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500195 } else {
Jason Monk1ffa11b2016-03-08 14:44:23 -0500196 if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser);
Jason Monkd5a204f2015-12-21 08:50:01 -0500197 // Give it another chance next time it needs to be bound, out of kindness.
198 mBindTryCount = 0;
Jason Monkfe8f6822015-12-21 15:12:01 -0500199 mWrapper = null;
Jason Monk6d21e0d2016-05-25 11:37:47 -0400200 if (mIsBound) {
201 mContext.unbindService(this);
202 mIsBound = false;
203 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500204 }
205 }
206
207 @Override
208 public void onServiceConnected(ComponentName name, IBinder service) {
209 if (DEBUG) Log.d(TAG, "onServiceConnected " + name);
210 // Got a connection, set the binding count to 0.
211 mBindTryCount = 0;
Jason Monk29c93ce2016-04-20 11:41:36 -0400212 final QSTileServiceWrapper wrapper = new QSTileServiceWrapper(Stub.asInterface(service));
Jason Monkd5a204f2015-12-21 08:50:01 -0500213 try {
214 service.linkToDeath(this, 0);
215 } catch (RemoteException e) {
216 }
Jason Monk29c93ce2016-04-20 11:41:36 -0400217 mWrapper = wrapper;
Jason Monkd5a204f2015-12-21 08:50:01 -0500218 handlePendingMessages();
219 }
220
221 @Override
222 public void onServiceDisconnected(ComponentName name) {
223 if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name);
Jason Monk624cbe22016-05-02 10:42:17 -0400224 handleDeath();
Jason Monkd5a204f2015-12-21 08:50:01 -0500225 }
226
227 private void handlePendingMessages() {
228 // This ordering is laid out manually to make sure we preserve the TileService
229 // lifecycle.
230 ArraySet<Integer> queue;
231 synchronized (mQueuedMessages) {
232 queue = new ArraySet<>(mQueuedMessages);
233 mQueuedMessages.clear();
234 }
235 if (queue.contains(MSG_ON_ADDED)) {
236 if (DEBUG) Log.d(TAG, "Handling pending onAdded");
237 onTileAdded();
238 }
239 if (mListening) {
240 if (DEBUG) Log.d(TAG, "Handling pending onStartListening");
Jason Monkd5a204f2015-12-21 08:50:01 -0500241 onStartListening();
242 }
243 if (queue.contains(MSG_ON_CLICK)) {
244 if (DEBUG) Log.d(TAG, "Handling pending onClick");
245 if (!mListening) {
246 Log.w(TAG, "Managed to get click on non-listening state...");
247 // Skipping click since lost click privileges.
248 } else {
249 onClick(mClickBinder);
250 }
251 }
Jason Monk94295132016-01-12 11:27:02 -0500252 if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) {
253 if (DEBUG) Log.d(TAG, "Handling pending onUnlockComplete");
254 if (!mListening) {
255 Log.w(TAG, "Managed to get unlock on non-listening state...");
256 // Skipping unlock since lost click privileges.
257 } else {
258 onUnlockComplete();
259 }
260 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500261 if (queue.contains(MSG_ON_REMOVED)) {
262 if (DEBUG) Log.d(TAG, "Handling pending onRemoved");
263 if (mListening) {
264 Log.w(TAG, "Managed to get remove in listening state...");
265 onStopListening();
266 }
267 onTileRemoved();
268 }
Jason Monk51c444b2016-01-06 16:32:29 -0500269 if (mUnbindImmediate) {
270 mUnbindImmediate = false;
271 setBindService(false);
272 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500273 }
274
275 public void handleDestroy() {
276 if (DEBUG) Log.d(TAG, "handleDestroy");
277 if (mReceiverRegistered) {
278 stopPackageListening();
279 }
280 }
281
282 private void handleDeath() {
283 if (mWrapper == null) return;
284 mWrapper = null;
285 if (!mBound) return;
286 if (DEBUG) Log.d(TAG, "handleDeath");
287 if (checkComponentState()) {
288 mHandler.postDelayed(new Runnable() {
289 @Override
290 public void run() {
291 if (mBound) {
292 // Retry binding.
293 setBindService(true);
294 }
295 }
Geoffrey Pitschebee1a32016-09-09 13:04:12 -0400296 }, mBindRetryDelay);
Jason Monkd5a204f2015-12-21 08:50:01 -0500297 }
298 }
299
Jason Monkd5a204f2015-12-21 08:50:01 -0500300 private boolean checkComponentState() {
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400301 if (!isPackageAvailable() || !isComponentAvailable()) {
Jason Monkd5a204f2015-12-21 08:50:01 -0500302 startPackageListening();
303 return false;
304 }
305 return true;
306 }
307
308 private void startPackageListening() {
309 if (DEBUG) Log.d(TAG, "startPackageListening");
310 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
311 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
312 filter.addDataScheme("package");
313 mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler);
Jason Monk1c2fea82016-03-11 11:33:36 -0500314 filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
Fabian Kozynski5ca7a512019-10-16 19:56:11 +0000315 mBroadcastDispatcher.registerReceiver(this, filter, mHandler, mUser);
Jason Monkd5a204f2015-12-21 08:50:01 -0500316 mReceiverRegistered = true;
317 }
318
319 private void stopPackageListening() {
320 if (DEBUG) Log.d(TAG, "stopPackageListening");
321 mContext.unregisterReceiver(this);
Fabian Kozynski5ca7a512019-10-16 19:56:11 +0000322 mBroadcastDispatcher.unregisterReceiver(this);
Jason Monkd5a204f2015-12-21 08:50:01 -0500323 mReceiverRegistered = false;
324 }
325
Jason Monk624cbe22016-05-02 10:42:17 -0400326 public void setTileChangeListener(TileChangeListener changeListener) {
327 mChangeListener = changeListener;
328 }
329
Jason Monkd5a204f2015-12-21 08:50:01 -0500330 @Override
331 public void onReceive(Context context, Intent intent) {
332 if (DEBUG) Log.d(TAG, "onReceive: " + intent);
Jason Monk1c2fea82016-03-11 11:33:36 -0500333 if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
334 Uri data = intent.getData();
335 String pkgName = data.getEncodedSchemeSpecificPart();
Narayan Kamath607223f2018-02-19 14:09:02 +0000336 if (!Objects.equals(pkgName, mIntent.getComponent().getPackageName())) {
Jason Monk1c2fea82016-03-11 11:33:36 -0500337 return;
338 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500339 }
Jason Monk624cbe22016-05-02 10:42:17 -0400340 if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) && mChangeListener != null) {
341 mChangeListener.onTileChanged(mIntent.getComponent());
342 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500343 stopPackageListening();
344 if (mBound) {
345 // Trying to bind again will check the state of the package before bothering to bind.
346 if (DEBUG) Log.d(TAG, "Trying to rebind");
347 setBindService(true);
348 }
349 }
350
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400351 private boolean isComponentAvailable() {
Jason Monkd5a204f2015-12-21 08:50:01 -0500352 String packageName = mIntent.getComponent().getPackageName();
353 try {
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400354 ServiceInfo si = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
Jason Monkd5a204f2015-12-21 08:50:01 -0500355 0, mUser.getIdentifier());
356 if (DEBUG && si == null) Log.d(TAG, "Can't find component " + mIntent.getComponent());
357 return si != null;
358 } catch (RemoteException e) {
359 // Shouldn't happen.
360 }
361 return false;
362 }
363
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400364 private boolean isPackageAvailable() {
Jason Monkd5a204f2015-12-21 08:50:01 -0500365 String packageName = mIntent.getComponent().getPackageName();
366 try {
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400367 mPackageManagerAdapter.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier());
Jason Monkd5a204f2015-12-21 08:50:01 -0500368 return true;
369 } catch (PackageManager.NameNotFoundException e) {
370 if (DEBUG) Log.d(TAG, "Package not available: " + packageName, e);
371 else Log.d(TAG, "Package not available: " + packageName);
372 }
373 return false;
374 }
375
376 private void queueMessage(int message) {
377 synchronized (mQueuedMessages) {
378 mQueuedMessages.add(message);
379 }
380 }
381
382 @Override
Jason Monkd5a204f2015-12-21 08:50:01 -0500383 public void onTileAdded() {
384 if (DEBUG) Log.d(TAG, "onTileAdded");
385 if (mWrapper == null || !mWrapper.onTileAdded()) {
386 queueMessage(MSG_ON_ADDED);
387 handleDeath();
388 }
389 }
390
391 @Override
392 public void onTileRemoved() {
393 if (DEBUG) Log.d(TAG, "onTileRemoved");
394 if (mWrapper == null || !mWrapper.onTileRemoved()) {
395 queueMessage(MSG_ON_REMOVED);
396 handleDeath();
397 }
398 }
399
400 @Override
401 public void onStartListening() {
402 if (DEBUG) Log.d(TAG, "onStartListening");
403 mListening = true;
404 if (mWrapper != null && !mWrapper.onStartListening()) {
405 handleDeath();
406 }
407 }
408
409 @Override
410 public void onStopListening() {
411 if (DEBUG) Log.d(TAG, "onStopListening");
412 mListening = false;
413 if (mWrapper != null && !mWrapper.onStopListening()) {
414 handleDeath();
415 }
416 }
417
418 @Override
419 public void onClick(IBinder iBinder) {
Jason Monk1ffa11b2016-03-08 14:44:23 -0500420 if (DEBUG) Log.d(TAG, "onClick " + iBinder + " " + mUser);
Jason Monkd5a204f2015-12-21 08:50:01 -0500421 if (mWrapper == null || !mWrapper.onClick(iBinder)) {
422 mClickBinder = iBinder;
423 queueMessage(MSG_ON_CLICK);
424 handleDeath();
425 }
426 }
427
428 @Override
Jason Monk94295132016-01-12 11:27:02 -0500429 public void onUnlockComplete() {
430 if (DEBUG) Log.d(TAG, "onUnlockComplete");
431 if (mWrapper == null || !mWrapper.onUnlockComplete()) {
432 queueMessage(MSG_ON_UNLOCK_COMPLETE);
433 handleDeath();
434 }
435 }
436
437 @Override
Jason Monkd5a204f2015-12-21 08:50:01 -0500438 public IBinder asBinder() {
439 return mWrapper != null ? mWrapper.asBinder() : null;
440 }
441
442 @Override
443 public void binderDied() {
444 if (DEBUG) Log.d(TAG, "binderDeath");
445 handleDeath();
446 }
Jason Monk624cbe22016-05-02 10:42:17 -0400447
Jason Monkee68fd82016-06-23 13:12:23 -0400448 public IBinder getToken() {
449 return mToken;
450 }
451
Jason Monk624cbe22016-05-02 10:42:17 -0400452 public interface TileChangeListener {
453 void onTileChanged(ComponentName tile);
454 }
Jason Monkbaade752016-08-25 15:57:14 -0400455
456 public static boolean isTileAdded(Context context, ComponentName component) {
457 return context.getSharedPreferences(TILES, 0).getBoolean(component.flattenToString(), false);
458 }
459
460 public static void setTileAdded(Context context, ComponentName component, boolean added) {
461 context.getSharedPreferences(TILES, 0).edit().putBoolean(component.flattenToString(),
462 added).commit();
463 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500464}