blob: f59e0c2d9bc2338999603db29afff68f5470eb69 [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
Narayan Kamath607223f2018-02-19 14:09:02 +000041import java.util.Objects;
Jason Monkd5a204f2015-12-21 08:50:01 -050042import 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;
Jason Monk94295132016-01-12 11:27:02 -050060 private static final int MSG_ON_UNLOCK_COMPLETE = 3;
Jason Monkd5a204f2015-12-21 08:50:01 -050061
62 // Bind retry control.
63 private static final int MAX_BIND_RETRIES = 5;
Geoffrey Pitschebee1a32016-09-09 13:04:12 -040064 private static final int DEFAULT_BIND_RETRY_DELAY = 1000;
Jason Monkd5a204f2015-12-21 08:50:01 -050065
Jason Monkbaade752016-08-25 15:57:14 -040066 // Shared prefs that hold tile lifecycle info.
67 private static final String TILES = "tiles_prefs";
68
Jason Monkd5a204f2015-12-21 08:50:01 -050069 private final Context mContext;
70 private final Handler mHandler;
71 private final Intent mIntent;
72 private final UserHandle mUser;
Jason Monkee68fd82016-06-23 13:12:23 -040073 private final IBinder mToken = new Binder();
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -040074 private final PackageManagerAdapter mPackageManagerAdapter;
Jason Monkd5a204f2015-12-21 08:50:01 -050075
76 private Set<Integer> mQueuedMessages = new ArraySet<>();
77 private QSTileServiceWrapper mWrapper;
78 private boolean mListening;
Jason Monkd5a204f2015-12-21 08:50:01 -050079 private IBinder mClickBinder;
80
81 private int mBindTryCount;
Geoffrey Pitschebee1a32016-09-09 13:04:12 -040082 private int mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY;
Jason Monkd5a204f2015-12-21 08:50:01 -050083 private boolean mBound;
Jason Monkd5a204f2015-12-21 08:50:01 -050084 boolean mReceiverRegistered;
Jason Monk51c444b2016-01-06 16:32:29 -050085 private boolean mUnbindImmediate;
Jason Monk624cbe22016-05-02 10:42:17 -040086 private TileChangeListener mChangeListener;
Jason Monk6d21e0d2016-05-25 11:37:47 -040087 // Return value from bindServiceAsUser, determines whether safe to call unbind.
88 private boolean mIsBound;
Jason Monkd5a204f2015-12-21 08:50:01 -050089
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -040090 public TileLifecycleManager(Handler handler, Context context, IQSService service, Tile tile,
91 Intent intent, UserHandle user) {
92 this(handler, context, service, tile, intent, user, new PackageManagerAdapter(context));
93 }
94
95 @VisibleForTesting
96 TileLifecycleManager(Handler handler, Context context, IQSService service, Tile tile,
97 Intent intent, UserHandle user, PackageManagerAdapter packageManagerAdapter) {
Jason Monkd5a204f2015-12-21 08:50:01 -050098 mContext = context;
99 mHandler = handler;
100 mIntent = intent;
Jason Monka3453b8b2016-06-17 12:42:59 -0400101 mIntent.putExtra(TileService.EXTRA_SERVICE, service.asBinder());
Jason Monkee68fd82016-06-23 13:12:23 -0400102 mIntent.putExtra(TileService.EXTRA_TOKEN, mToken);
Jason Monkd5a204f2015-12-21 08:50:01 -0500103 mUser = user;
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400104 mPackageManagerAdapter = packageManagerAdapter;
Jason Monk1ffa11b2016-03-08 14:44:23 -0500105 if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser);
Jason Monkd5a204f2015-12-21 08:50:01 -0500106 }
107
Jason Monkfe8f6822015-12-21 15:12:01 -0500108 public ComponentName getComponent() {
109 return mIntent.getComponent();
110 }
111
Jason Monkd5a204f2015-12-21 08:50:01 -0500112 public boolean hasPendingClick() {
113 synchronized (mQueuedMessages) {
114 return mQueuedMessages.contains(MSG_ON_CLICK);
115 }
116 }
117
Geoffrey Pitschebee1a32016-09-09 13:04:12 -0400118 public void setBindRetryDelay(int delayMs) {
119 mBindRetryDelay = delayMs;
120 }
121
Jason Monk97d22722016-04-07 11:41:47 -0400122 public boolean isActiveTile() {
123 try {
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400124 ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
Jason Monk97d22722016-04-07 11:41:47 -0400125 PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
126 return info.metaData != null
127 && info.metaData.getBoolean(TileService.META_DATA_ACTIVE_TILE, false);
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400128 } catch (PackageManager.NameNotFoundException e) {
Jason Monk97d22722016-04-07 11:41:47 -0400129 return false;
130 }
131 }
132
Jason Monk51c444b2016-01-06 16:32:29 -0500133 /**
Fabian Kozynski05843f02019-06-28 13:19:57 -0400134 * Determines whether the associated TileService is a Boolean Tile.
135 *
136 * @return true if {@link TileService#META_DATA_BOOLEAN_TILE} is set to {@code true} for this
137 * tile
138 * @see TileService#META_DATA_BOOLEAN_TILE
139 */
140 public boolean isBooleanTile() {
141 try {
142 ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
143 PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
144 return info.metaData != null
145 && info.metaData.getBoolean(TileService.META_DATA_BOOLEAN_TILE, false);
146 } catch (PackageManager.NameNotFoundException e) {
147 return false;
148 }
149 }
150
151 /**
Jason Monk51c444b2016-01-06 16:32:29 -0500152 * Binds just long enough to send any queued messages, then unbinds.
153 */
154 public void flushMessagesAndUnbind() {
155 mUnbindImmediate = true;
156 setBindService(true);
157 }
158
Jason Monkd5a204f2015-12-21 08:50:01 -0500159 public void setBindService(boolean bind) {
Jason Monk34f6cbc2016-08-30 16:33:48 -0400160 if (mBound && mUnbindImmediate) {
161 // If we are already bound and expecting to unbind, this means we should stay bound
162 // because something else wants to hold the connection open.
163 mUnbindImmediate = false;
164 return;
165 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500166 mBound = bind;
167 if (bind) {
168 if (mBindTryCount == MAX_BIND_RETRIES) {
169 // Too many failures, give up on this tile until an update.
170 startPackageListening();
171 return;
172 }
173 if (!checkComponentState()) {
174 return;
175 }
Jason Monk1ffa11b2016-03-08 14:44:23 -0500176 if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser);
Jason Monkd5a204f2015-12-21 08:50:01 -0500177 mBindTryCount++;
Will Harmon604c2f92016-06-06 12:54:59 -0700178 try {
179 mIsBound = mContext.bindServiceAsUser(mIntent, this,
Fabian Kozynskiff888da2019-07-01 12:50:03 -0400180 Context.BIND_AUTO_CREATE
181 | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
182 | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
183 | Context.BIND_WAIVE_PRIORITY,
184 mUser);
Will Harmon604c2f92016-06-06 12:54:59 -0700185 } catch (SecurityException e) {
186 Log.e(TAG, "Failed to bind to service", e);
187 mIsBound = false;
188 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500189 } else {
Jason Monk1ffa11b2016-03-08 14:44:23 -0500190 if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser);
Jason Monkd5a204f2015-12-21 08:50:01 -0500191 // Give it another chance next time it needs to be bound, out of kindness.
192 mBindTryCount = 0;
Jason Monkfe8f6822015-12-21 15:12:01 -0500193 mWrapper = null;
Jason Monk6d21e0d2016-05-25 11:37:47 -0400194 if (mIsBound) {
195 mContext.unbindService(this);
196 mIsBound = false;
197 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500198 }
199 }
200
201 @Override
202 public void onServiceConnected(ComponentName name, IBinder service) {
203 if (DEBUG) Log.d(TAG, "onServiceConnected " + name);
204 // Got a connection, set the binding count to 0.
205 mBindTryCount = 0;
Jason Monk29c93ce2016-04-20 11:41:36 -0400206 final QSTileServiceWrapper wrapper = new QSTileServiceWrapper(Stub.asInterface(service));
Jason Monkd5a204f2015-12-21 08:50:01 -0500207 try {
208 service.linkToDeath(this, 0);
209 } catch (RemoteException e) {
210 }
Jason Monk29c93ce2016-04-20 11:41:36 -0400211 mWrapper = wrapper;
Jason Monkd5a204f2015-12-21 08:50:01 -0500212 handlePendingMessages();
213 }
214
215 @Override
216 public void onServiceDisconnected(ComponentName name) {
217 if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name);
Jason Monk624cbe22016-05-02 10:42:17 -0400218 handleDeath();
Jason Monkd5a204f2015-12-21 08:50:01 -0500219 }
220
221 private void handlePendingMessages() {
222 // This ordering is laid out manually to make sure we preserve the TileService
223 // lifecycle.
224 ArraySet<Integer> queue;
225 synchronized (mQueuedMessages) {
226 queue = new ArraySet<>(mQueuedMessages);
227 mQueuedMessages.clear();
228 }
229 if (queue.contains(MSG_ON_ADDED)) {
230 if (DEBUG) Log.d(TAG, "Handling pending onAdded");
231 onTileAdded();
232 }
233 if (mListening) {
234 if (DEBUG) Log.d(TAG, "Handling pending onStartListening");
Jason Monkd5a204f2015-12-21 08:50:01 -0500235 onStartListening();
236 }
237 if (queue.contains(MSG_ON_CLICK)) {
238 if (DEBUG) Log.d(TAG, "Handling pending onClick");
239 if (!mListening) {
240 Log.w(TAG, "Managed to get click on non-listening state...");
241 // Skipping click since lost click privileges.
242 } else {
243 onClick(mClickBinder);
244 }
245 }
Jason Monk94295132016-01-12 11:27:02 -0500246 if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) {
247 if (DEBUG) Log.d(TAG, "Handling pending onUnlockComplete");
248 if (!mListening) {
249 Log.w(TAG, "Managed to get unlock on non-listening state...");
250 // Skipping unlock since lost click privileges.
251 } else {
252 onUnlockComplete();
253 }
254 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500255 if (queue.contains(MSG_ON_REMOVED)) {
256 if (DEBUG) Log.d(TAG, "Handling pending onRemoved");
257 if (mListening) {
258 Log.w(TAG, "Managed to get remove in listening state...");
259 onStopListening();
260 }
261 onTileRemoved();
262 }
Jason Monk51c444b2016-01-06 16:32:29 -0500263 if (mUnbindImmediate) {
264 mUnbindImmediate = false;
265 setBindService(false);
266 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500267 }
268
269 public void handleDestroy() {
270 if (DEBUG) Log.d(TAG, "handleDestroy");
271 if (mReceiverRegistered) {
272 stopPackageListening();
273 }
274 }
275
276 private void handleDeath() {
277 if (mWrapper == null) return;
278 mWrapper = null;
279 if (!mBound) return;
280 if (DEBUG) Log.d(TAG, "handleDeath");
281 if (checkComponentState()) {
282 mHandler.postDelayed(new Runnable() {
283 @Override
284 public void run() {
285 if (mBound) {
286 // Retry binding.
287 setBindService(true);
288 }
289 }
Geoffrey Pitschebee1a32016-09-09 13:04:12 -0400290 }, mBindRetryDelay);
Jason Monkd5a204f2015-12-21 08:50:01 -0500291 }
292 }
293
Jason Monkd5a204f2015-12-21 08:50:01 -0500294 private boolean checkComponentState() {
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400295 if (!isPackageAvailable() || !isComponentAvailable()) {
Jason Monkd5a204f2015-12-21 08:50:01 -0500296 startPackageListening();
297 return false;
298 }
299 return true;
300 }
301
302 private void startPackageListening() {
303 if (DEBUG) Log.d(TAG, "startPackageListening");
304 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
305 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
306 filter.addDataScheme("package");
307 mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler);
Jason Monk1c2fea82016-03-11 11:33:36 -0500308 filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
309 mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler);
Jason Monkd5a204f2015-12-21 08:50:01 -0500310 mReceiverRegistered = true;
311 }
312
313 private void stopPackageListening() {
314 if (DEBUG) Log.d(TAG, "stopPackageListening");
315 mContext.unregisterReceiver(this);
316 mReceiverRegistered = false;
317 }
318
Jason Monk624cbe22016-05-02 10:42:17 -0400319 public void setTileChangeListener(TileChangeListener changeListener) {
320 mChangeListener = changeListener;
321 }
322
Jason Monkd5a204f2015-12-21 08:50:01 -0500323 @Override
324 public void onReceive(Context context, Intent intent) {
325 if (DEBUG) Log.d(TAG, "onReceive: " + intent);
Jason Monk1c2fea82016-03-11 11:33:36 -0500326 if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
327 Uri data = intent.getData();
328 String pkgName = data.getEncodedSchemeSpecificPart();
Narayan Kamath607223f2018-02-19 14:09:02 +0000329 if (!Objects.equals(pkgName, mIntent.getComponent().getPackageName())) {
Jason Monk1c2fea82016-03-11 11:33:36 -0500330 return;
331 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500332 }
Jason Monk624cbe22016-05-02 10:42:17 -0400333 if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) && mChangeListener != null) {
334 mChangeListener.onTileChanged(mIntent.getComponent());
335 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500336 stopPackageListening();
337 if (mBound) {
338 // Trying to bind again will check the state of the package before bothering to bind.
339 if (DEBUG) Log.d(TAG, "Trying to rebind");
340 setBindService(true);
341 }
342 }
343
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400344 private boolean isComponentAvailable() {
Jason Monkd5a204f2015-12-21 08:50:01 -0500345 String packageName = mIntent.getComponent().getPackageName();
346 try {
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400347 ServiceInfo si = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
Jason Monkd5a204f2015-12-21 08:50:01 -0500348 0, mUser.getIdentifier());
349 if (DEBUG && si == null) Log.d(TAG, "Can't find component " + mIntent.getComponent());
350 return si != null;
351 } catch (RemoteException e) {
352 // Shouldn't happen.
353 }
354 return false;
355 }
356
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400357 private boolean isPackageAvailable() {
Jason Monkd5a204f2015-12-21 08:50:01 -0500358 String packageName = mIntent.getComponent().getPackageName();
359 try {
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400360 mPackageManagerAdapter.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier());
Jason Monkd5a204f2015-12-21 08:50:01 -0500361 return true;
362 } catch (PackageManager.NameNotFoundException e) {
363 if (DEBUG) Log.d(TAG, "Package not available: " + packageName, e);
364 else Log.d(TAG, "Package not available: " + packageName);
365 }
366 return false;
367 }
368
369 private void queueMessage(int message) {
370 synchronized (mQueuedMessages) {
371 mQueuedMessages.add(message);
372 }
373 }
374
375 @Override
Jason Monkd5a204f2015-12-21 08:50:01 -0500376 public void onTileAdded() {
377 if (DEBUG) Log.d(TAG, "onTileAdded");
378 if (mWrapper == null || !mWrapper.onTileAdded()) {
379 queueMessage(MSG_ON_ADDED);
380 handleDeath();
381 }
382 }
383
384 @Override
385 public void onTileRemoved() {
386 if (DEBUG) Log.d(TAG, "onTileRemoved");
387 if (mWrapper == null || !mWrapper.onTileRemoved()) {
388 queueMessage(MSG_ON_REMOVED);
389 handleDeath();
390 }
391 }
392
393 @Override
394 public void onStartListening() {
395 if (DEBUG) Log.d(TAG, "onStartListening");
396 mListening = true;
397 if (mWrapper != null && !mWrapper.onStartListening()) {
398 handleDeath();
399 }
400 }
401
402 @Override
403 public void onStopListening() {
404 if (DEBUG) Log.d(TAG, "onStopListening");
405 mListening = false;
406 if (mWrapper != null && !mWrapper.onStopListening()) {
407 handleDeath();
408 }
409 }
410
411 @Override
412 public void onClick(IBinder iBinder) {
Jason Monk1ffa11b2016-03-08 14:44:23 -0500413 if (DEBUG) Log.d(TAG, "onClick " + iBinder + " " + mUser);
Jason Monkd5a204f2015-12-21 08:50:01 -0500414 if (mWrapper == null || !mWrapper.onClick(iBinder)) {
415 mClickBinder = iBinder;
416 queueMessage(MSG_ON_CLICK);
417 handleDeath();
418 }
419 }
420
421 @Override
Jason Monk94295132016-01-12 11:27:02 -0500422 public void onUnlockComplete() {
423 if (DEBUG) Log.d(TAG, "onUnlockComplete");
424 if (mWrapper == null || !mWrapper.onUnlockComplete()) {
425 queueMessage(MSG_ON_UNLOCK_COMPLETE);
426 handleDeath();
427 }
428 }
429
430 @Override
Jason Monkd5a204f2015-12-21 08:50:01 -0500431 public IBinder asBinder() {
432 return mWrapper != null ? mWrapper.asBinder() : null;
433 }
434
435 @Override
436 public void binderDied() {
437 if (DEBUG) Log.d(TAG, "binderDeath");
438 handleDeath();
439 }
Jason Monk624cbe22016-05-02 10:42:17 -0400440
Jason Monkee68fd82016-06-23 13:12:23 -0400441 public IBinder getToken() {
442 return mToken;
443 }
444
Jason Monk624cbe22016-05-02 10:42:17 -0400445 public interface TileChangeListener {
446 void onTileChanged(ComponentName tile);
447 }
Jason Monkbaade752016-08-25 15:57:14 -0400448
449 public static boolean isTileAdded(Context context, ComponentName component) {
450 return context.getSharedPreferences(TILES, 0).getBoolean(component.flattenToString(), false);
451 }
452
453 public static void setTileAdded(Context context, ComponentName component, boolean added) {
454 context.getSharedPreferences(TILES, 0).edit().putBoolean(component.flattenToString(),
455 added).commit();
456 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500457}