blob: b135f7b27c3ff0e4240bf80a6f9661ab199dcae3 [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 /**
134 * Binds just long enough to send any queued messages, then unbinds.
135 */
136 public void flushMessagesAndUnbind() {
137 mUnbindImmediate = true;
138 setBindService(true);
139 }
140
Jason Monkd5a204f2015-12-21 08:50:01 -0500141 public void setBindService(boolean bind) {
Jason Monk34f6cbc2016-08-30 16:33:48 -0400142 if (mBound && mUnbindImmediate) {
143 // If we are already bound and expecting to unbind, this means we should stay bound
144 // because something else wants to hold the connection open.
145 mUnbindImmediate = false;
146 return;
147 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500148 mBound = bind;
149 if (bind) {
150 if (mBindTryCount == MAX_BIND_RETRIES) {
151 // Too many failures, give up on this tile until an update.
152 startPackageListening();
153 return;
154 }
155 if (!checkComponentState()) {
156 return;
157 }
Jason Monk1ffa11b2016-03-08 14:44:23 -0500158 if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser);
Jason Monkd5a204f2015-12-21 08:50:01 -0500159 mBindTryCount++;
Will Harmon604c2f92016-06-06 12:54:59 -0700160 try {
161 mIsBound = mContext.bindServiceAsUser(mIntent, this,
Michal Karpinskie069b002019-03-07 16:15:14 +0000162 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
163 | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, mUser);
Will Harmon604c2f92016-06-06 12:54:59 -0700164 } catch (SecurityException e) {
165 Log.e(TAG, "Failed to bind to service", e);
166 mIsBound = false;
167 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500168 } else {
Jason Monk1ffa11b2016-03-08 14:44:23 -0500169 if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser);
Jason Monkd5a204f2015-12-21 08:50:01 -0500170 // Give it another chance next time it needs to be bound, out of kindness.
171 mBindTryCount = 0;
Jason Monkfe8f6822015-12-21 15:12:01 -0500172 mWrapper = null;
Jason Monk6d21e0d2016-05-25 11:37:47 -0400173 if (mIsBound) {
174 mContext.unbindService(this);
175 mIsBound = false;
176 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500177 }
178 }
179
180 @Override
181 public void onServiceConnected(ComponentName name, IBinder service) {
182 if (DEBUG) Log.d(TAG, "onServiceConnected " + name);
183 // Got a connection, set the binding count to 0.
184 mBindTryCount = 0;
Jason Monk29c93ce2016-04-20 11:41:36 -0400185 final QSTileServiceWrapper wrapper = new QSTileServiceWrapper(Stub.asInterface(service));
Jason Monkd5a204f2015-12-21 08:50:01 -0500186 try {
187 service.linkToDeath(this, 0);
188 } catch (RemoteException e) {
189 }
Jason Monk29c93ce2016-04-20 11:41:36 -0400190 mWrapper = wrapper;
Jason Monkd5a204f2015-12-21 08:50:01 -0500191 handlePendingMessages();
192 }
193
194 @Override
195 public void onServiceDisconnected(ComponentName name) {
196 if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name);
Jason Monk624cbe22016-05-02 10:42:17 -0400197 handleDeath();
Jason Monkd5a204f2015-12-21 08:50:01 -0500198 }
199
200 private void handlePendingMessages() {
201 // This ordering is laid out manually to make sure we preserve the TileService
202 // lifecycle.
203 ArraySet<Integer> queue;
204 synchronized (mQueuedMessages) {
205 queue = new ArraySet<>(mQueuedMessages);
206 mQueuedMessages.clear();
207 }
208 if (queue.contains(MSG_ON_ADDED)) {
209 if (DEBUG) Log.d(TAG, "Handling pending onAdded");
210 onTileAdded();
211 }
212 if (mListening) {
213 if (DEBUG) Log.d(TAG, "Handling pending onStartListening");
Jason Monkd5a204f2015-12-21 08:50:01 -0500214 onStartListening();
215 }
216 if (queue.contains(MSG_ON_CLICK)) {
217 if (DEBUG) Log.d(TAG, "Handling pending onClick");
218 if (!mListening) {
219 Log.w(TAG, "Managed to get click on non-listening state...");
220 // Skipping click since lost click privileges.
221 } else {
222 onClick(mClickBinder);
223 }
224 }
Jason Monk94295132016-01-12 11:27:02 -0500225 if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) {
226 if (DEBUG) Log.d(TAG, "Handling pending onUnlockComplete");
227 if (!mListening) {
228 Log.w(TAG, "Managed to get unlock on non-listening state...");
229 // Skipping unlock since lost click privileges.
230 } else {
231 onUnlockComplete();
232 }
233 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500234 if (queue.contains(MSG_ON_REMOVED)) {
235 if (DEBUG) Log.d(TAG, "Handling pending onRemoved");
236 if (mListening) {
237 Log.w(TAG, "Managed to get remove in listening state...");
238 onStopListening();
239 }
240 onTileRemoved();
241 }
Jason Monk51c444b2016-01-06 16:32:29 -0500242 if (mUnbindImmediate) {
243 mUnbindImmediate = false;
244 setBindService(false);
245 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500246 }
247
248 public void handleDestroy() {
249 if (DEBUG) Log.d(TAG, "handleDestroy");
250 if (mReceiverRegistered) {
251 stopPackageListening();
252 }
253 }
254
255 private void handleDeath() {
256 if (mWrapper == null) return;
257 mWrapper = null;
258 if (!mBound) return;
259 if (DEBUG) Log.d(TAG, "handleDeath");
260 if (checkComponentState()) {
261 mHandler.postDelayed(new Runnable() {
262 @Override
263 public void run() {
264 if (mBound) {
265 // Retry binding.
266 setBindService(true);
267 }
268 }
Geoffrey Pitschebee1a32016-09-09 13:04:12 -0400269 }, mBindRetryDelay);
Jason Monkd5a204f2015-12-21 08:50:01 -0500270 }
271 }
272
Jason Monkd5a204f2015-12-21 08:50:01 -0500273 private boolean checkComponentState() {
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400274 if (!isPackageAvailable() || !isComponentAvailable()) {
Jason Monkd5a204f2015-12-21 08:50:01 -0500275 startPackageListening();
276 return false;
277 }
278 return true;
279 }
280
281 private void startPackageListening() {
282 if (DEBUG) Log.d(TAG, "startPackageListening");
283 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
284 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
285 filter.addDataScheme("package");
286 mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler);
Jason Monk1c2fea82016-03-11 11:33:36 -0500287 filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
288 mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler);
Jason Monkd5a204f2015-12-21 08:50:01 -0500289 mReceiverRegistered = true;
290 }
291
292 private void stopPackageListening() {
293 if (DEBUG) Log.d(TAG, "stopPackageListening");
294 mContext.unregisterReceiver(this);
295 mReceiverRegistered = false;
296 }
297
Jason Monk624cbe22016-05-02 10:42:17 -0400298 public void setTileChangeListener(TileChangeListener changeListener) {
299 mChangeListener = changeListener;
300 }
301
Jason Monkd5a204f2015-12-21 08:50:01 -0500302 @Override
303 public void onReceive(Context context, Intent intent) {
304 if (DEBUG) Log.d(TAG, "onReceive: " + intent);
Jason Monk1c2fea82016-03-11 11:33:36 -0500305 if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
306 Uri data = intent.getData();
307 String pkgName = data.getEncodedSchemeSpecificPart();
Narayan Kamath607223f2018-02-19 14:09:02 +0000308 if (!Objects.equals(pkgName, mIntent.getComponent().getPackageName())) {
Jason Monk1c2fea82016-03-11 11:33:36 -0500309 return;
310 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500311 }
Jason Monk624cbe22016-05-02 10:42:17 -0400312 if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) && mChangeListener != null) {
313 mChangeListener.onTileChanged(mIntent.getComponent());
314 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500315 stopPackageListening();
316 if (mBound) {
317 // Trying to bind again will check the state of the package before bothering to bind.
318 if (DEBUG) Log.d(TAG, "Trying to rebind");
319 setBindService(true);
320 }
321 }
322
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400323 private boolean isComponentAvailable() {
Jason Monkd5a204f2015-12-21 08:50:01 -0500324 String packageName = mIntent.getComponent().getPackageName();
325 try {
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400326 ServiceInfo si = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
Jason Monkd5a204f2015-12-21 08:50:01 -0500327 0, mUser.getIdentifier());
328 if (DEBUG && si == null) Log.d(TAG, "Can't find component " + mIntent.getComponent());
329 return si != null;
330 } catch (RemoteException e) {
331 // Shouldn't happen.
332 }
333 return false;
334 }
335
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400336 private boolean isPackageAvailable() {
Jason Monkd5a204f2015-12-21 08:50:01 -0500337 String packageName = mIntent.getComponent().getPackageName();
338 try {
Geoffrey Pitschaf2076a2016-08-31 12:44:08 -0400339 mPackageManagerAdapter.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier());
Jason Monkd5a204f2015-12-21 08:50:01 -0500340 return true;
341 } catch (PackageManager.NameNotFoundException e) {
342 if (DEBUG) Log.d(TAG, "Package not available: " + packageName, e);
343 else Log.d(TAG, "Package not available: " + packageName);
344 }
345 return false;
346 }
347
348 private void queueMessage(int message) {
349 synchronized (mQueuedMessages) {
350 mQueuedMessages.add(message);
351 }
352 }
353
354 @Override
Jason Monkd5a204f2015-12-21 08:50:01 -0500355 public void onTileAdded() {
356 if (DEBUG) Log.d(TAG, "onTileAdded");
357 if (mWrapper == null || !mWrapper.onTileAdded()) {
358 queueMessage(MSG_ON_ADDED);
359 handleDeath();
360 }
361 }
362
363 @Override
364 public void onTileRemoved() {
365 if (DEBUG) Log.d(TAG, "onTileRemoved");
366 if (mWrapper == null || !mWrapper.onTileRemoved()) {
367 queueMessage(MSG_ON_REMOVED);
368 handleDeath();
369 }
370 }
371
372 @Override
373 public void onStartListening() {
374 if (DEBUG) Log.d(TAG, "onStartListening");
375 mListening = true;
376 if (mWrapper != null && !mWrapper.onStartListening()) {
377 handleDeath();
378 }
379 }
380
381 @Override
382 public void onStopListening() {
383 if (DEBUG) Log.d(TAG, "onStopListening");
384 mListening = false;
385 if (mWrapper != null && !mWrapper.onStopListening()) {
386 handleDeath();
387 }
388 }
389
390 @Override
391 public void onClick(IBinder iBinder) {
Jason Monk1ffa11b2016-03-08 14:44:23 -0500392 if (DEBUG) Log.d(TAG, "onClick " + iBinder + " " + mUser);
Jason Monkd5a204f2015-12-21 08:50:01 -0500393 if (mWrapper == null || !mWrapper.onClick(iBinder)) {
394 mClickBinder = iBinder;
395 queueMessage(MSG_ON_CLICK);
396 handleDeath();
397 }
398 }
399
400 @Override
Jason Monk94295132016-01-12 11:27:02 -0500401 public void onUnlockComplete() {
402 if (DEBUG) Log.d(TAG, "onUnlockComplete");
403 if (mWrapper == null || !mWrapper.onUnlockComplete()) {
404 queueMessage(MSG_ON_UNLOCK_COMPLETE);
405 handleDeath();
406 }
407 }
408
409 @Override
Jason Monkd5a204f2015-12-21 08:50:01 -0500410 public IBinder asBinder() {
411 return mWrapper != null ? mWrapper.asBinder() : null;
412 }
413
414 @Override
415 public void binderDied() {
416 if (DEBUG) Log.d(TAG, "binderDeath");
417 handleDeath();
418 }
Jason Monk624cbe22016-05-02 10:42:17 -0400419
Jason Monkee68fd82016-06-23 13:12:23 -0400420 public IBinder getToken() {
421 return mToken;
422 }
423
Jason Monk624cbe22016-05-02 10:42:17 -0400424 public interface TileChangeListener {
425 void onTileChanged(ComponentName tile);
426 }
Jason Monkbaade752016-08-25 15:57:14 -0400427
428 public static boolean isTileAdded(Context context, ComponentName component) {
429 return context.getSharedPreferences(TILES, 0).getBoolean(component.flattenToString(), false);
430 }
431
432 public static void setTileAdded(Context context, ComponentName component, boolean added) {
433 context.getSharedPreferences(TILES, 0).edit().putBoolean(component.flattenToString(),
434 added).commit();
435 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500436}