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 */ 16 package com.android.systemui.qs.external; 17 18 import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT; 19 20 import android.app.compat.CompatChanges; 21 import android.content.BroadcastReceiver; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.content.ServiceConnection; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ServiceInfo; 29 import android.net.Uri; 30 import android.os.Binder; 31 import android.os.Handler; 32 import android.os.IBinder; 33 import android.os.RemoteException; 34 import android.os.UserHandle; 35 import android.service.quicksettings.IQSService; 36 import android.service.quicksettings.IQSTileService; 37 import android.service.quicksettings.TileService; 38 import android.util.ArraySet; 39 import android.util.Log; 40 41 import androidx.annotation.Nullable; 42 import androidx.annotation.WorkerThread; 43 44 import com.android.systemui.broadcast.BroadcastDispatcher; 45 import com.android.systemui.dagger.qualifiers.Background; 46 import com.android.systemui.dagger.qualifiers.Main; 47 import com.android.systemui.util.concurrency.DelayableExecutor; 48 49 import dagger.assisted.Assisted; 50 import dagger.assisted.AssistedFactory; 51 import dagger.assisted.AssistedInject; 52 53 import java.util.NoSuchElementException; 54 import java.util.Objects; 55 import java.util.Set; 56 import java.util.concurrent.atomic.AtomicBoolean; 57 58 /** 59 * Manages the lifecycle of a TileService. 60 * <p> 61 * Will keep track of all calls on the IQSTileService interface and will relay those calls to the 62 * TileService as soon as it is bound. It will only bind to the service when it is allowed to 63 * ({@link #setBindService(boolean)}) and when the service is available. 64 */ 65 public class TileLifecycleManager extends BroadcastReceiver implements 66 IQSTileService, ServiceConnection, IBinder.DeathRecipient { 67 public static final boolean DEBUG = false; 68 69 private static final String TAG = "TileLifecycleManager"; 70 71 private static final int META_DATA_QUERY_FLAGS = 72 PackageManager.GET_META_DATA 73 | PackageManager.MATCH_UNINSTALLED_PACKAGES 74 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 75 | PackageManager.MATCH_DIRECT_BOOT_AWARE; 76 77 private static final int MSG_ON_ADDED = 0; 78 private static final int MSG_ON_REMOVED = 1; 79 private static final int MSG_ON_CLICK = 2; 80 private static final int MSG_ON_UNLOCK_COMPLETE = 3; 81 82 // Bind retry control. 83 private static final int MAX_BIND_RETRIES = 5; 84 private static final int DEFAULT_BIND_RETRY_DELAY = 1000; 85 86 // Shared prefs that hold tile lifecycle info. 87 private static final String TILES = "tiles_prefs"; 88 89 private final Context mContext; 90 private final Handler mHandler; 91 private final Intent mIntent; 92 private final UserHandle mUser; 93 private final DelayableExecutor mExecutor; 94 private final IBinder mToken = new Binder(); 95 private final PackageManagerAdapter mPackageManagerAdapter; 96 private final BroadcastDispatcher mBroadcastDispatcher; 97 98 private Set<Integer> mQueuedMessages = new ArraySet<>(); 99 @Nullable 100 private QSTileServiceWrapper mWrapper; 101 private boolean mListening; 102 private IBinder mClickBinder; 103 104 private int mBindTryCount; 105 private int mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY; 106 private AtomicBoolean mBound = new AtomicBoolean(false); 107 private AtomicBoolean mPackageReceiverRegistered = new AtomicBoolean(false); 108 private AtomicBoolean mUserReceiverRegistered = new AtomicBoolean(false); 109 private AtomicBoolean mUnbindImmediate = new AtomicBoolean(false); 110 @Nullable 111 private TileChangeListener mChangeListener; 112 // Return value from bindServiceAsUser, determines whether safe to call unbind. 113 private AtomicBoolean mIsBound = new AtomicBoolean(false); 114 115 @AssistedInject TileLifecycleManager(@ain Handler handler, Context context, IQSService service, PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher, @Assisted Intent intent, @Assisted UserHandle user, @Background DelayableExecutor executor)116 TileLifecycleManager(@Main Handler handler, Context context, IQSService service, 117 PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher, 118 @Assisted Intent intent, @Assisted UserHandle user, 119 @Background DelayableExecutor executor) { 120 mContext = context; 121 mHandler = handler; 122 mIntent = intent; 123 mIntent.putExtra(TileService.EXTRA_SERVICE, service.asBinder()); 124 mIntent.putExtra(TileService.EXTRA_TOKEN, mToken); 125 mUser = user; 126 mExecutor = executor; 127 mPackageManagerAdapter = packageManagerAdapter; 128 mBroadcastDispatcher = broadcastDispatcher; 129 if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser); 130 } 131 132 /** Injectable factory for TileLifecycleManager. */ 133 @AssistedFactory 134 public interface Factory { 135 /** 136 * 137 */ create(Intent intent, UserHandle userHandle)138 TileLifecycleManager create(Intent intent, UserHandle userHandle); 139 } 140 getUserId()141 public int getUserId() { 142 return mUser.getIdentifier(); 143 } 144 getComponent()145 public ComponentName getComponent() { 146 return mIntent.getComponent(); 147 } 148 hasPendingClick()149 public boolean hasPendingClick() { 150 synchronized (mQueuedMessages) { 151 return mQueuedMessages.contains(MSG_ON_CLICK); 152 } 153 } 154 setBindRetryDelay(int delayMs)155 public void setBindRetryDelay(int delayMs) { 156 mBindRetryDelay = delayMs; 157 } 158 isActiveTile()159 public boolean isActiveTile() { 160 try { 161 ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), 162 META_DATA_QUERY_FLAGS); 163 return info.metaData != null 164 && info.metaData.getBoolean(TileService.META_DATA_ACTIVE_TILE, false); 165 } catch (PackageManager.NameNotFoundException e) { 166 return false; 167 } 168 } 169 170 /** 171 * Determines whether the associated TileService is a Boolean Tile. 172 * 173 * @return true if {@link TileService#META_DATA_TOGGLEABLE_TILE} is set to {@code true} for this 174 * tile 175 * @see TileService#META_DATA_TOGGLEABLE_TILE 176 */ isToggleableTile()177 public boolean isToggleableTile() { 178 try { 179 ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), 180 META_DATA_QUERY_FLAGS); 181 return info.metaData != null 182 && info.metaData.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false); 183 } catch (PackageManager.NameNotFoundException e) { 184 return false; 185 } 186 } 187 188 /** 189 * Binds just long enough to send any queued messages, then unbinds. 190 */ flushMessagesAndUnbind()191 public void flushMessagesAndUnbind() { 192 mExecutor.execute(() -> { 193 mUnbindImmediate.set(true); 194 setBindService(true); 195 }); 196 } 197 198 @WorkerThread setBindService(boolean bind)199 private void setBindService(boolean bind) { 200 if (mBound.get() && mUnbindImmediate.get()) { 201 // If we are already bound and expecting to unbind, this means we should stay bound 202 // because something else wants to hold the connection open. 203 mUnbindImmediate.set(false); 204 return; 205 } 206 mBound.set(bind); 207 if (bind) { 208 if (mBindTryCount == MAX_BIND_RETRIES) { 209 // Too many failures, give up on this tile until an update. 210 startPackageListening(); 211 return; 212 } 213 if (!checkComponentState()) { 214 return; 215 } 216 if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser); 217 mBindTryCount++; 218 try { 219 mIsBound.set(bindServices()); 220 if (!mIsBound.get()) { 221 mContext.unbindService(this); 222 } 223 } catch (SecurityException e) { 224 Log.e(TAG, "Failed to bind to service", e); 225 mIsBound.set(false); 226 } 227 } else { 228 if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser); 229 // Give it another chance next time it needs to be bound, out of kindness. 230 mBindTryCount = 0; 231 freeWrapper(); 232 if (mIsBound.get()) { 233 try { 234 mContext.unbindService(this); 235 } catch (Exception e) { 236 Log.e(TAG, "Failed to unbind service " 237 + mIntent.getComponent().flattenToShortString(), e); 238 } 239 mIsBound.set(false); 240 } 241 } 242 } 243 244 /** 245 * Binds or unbinds to IQSService 246 */ executeSetBindService(boolean bind)247 public void executeSetBindService(boolean bind) { 248 mExecutor.execute(() -> setBindService(bind)); 249 } 250 bindServices()251 private boolean bindServices() { 252 String packageName = mIntent.getComponent().getPackageName(); 253 if (CompatChanges.isChangeEnabled(START_ACTIVITY_NEEDS_PENDING_INTENT, packageName, 254 mUser)) { 255 return mContext.bindServiceAsUser(mIntent, this, 256 Context.BIND_AUTO_CREATE 257 | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE 258 | Context.BIND_WAIVE_PRIORITY, 259 mUser); 260 } 261 return mContext.bindServiceAsUser(mIntent, this, 262 Context.BIND_AUTO_CREATE 263 | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE 264 | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS 265 | Context.BIND_WAIVE_PRIORITY, 266 mUser); 267 } 268 269 @Override onServiceConnected(ComponentName name, IBinder service)270 public void onServiceConnected(ComponentName name, IBinder service) { 271 if (DEBUG) Log.d(TAG, "onServiceConnected " + name); 272 // Got a connection, set the binding count to 0. 273 mBindTryCount = 0; 274 final QSTileServiceWrapper wrapper = new QSTileServiceWrapper(Stub.asInterface(service)); 275 try { 276 service.linkToDeath(this, 0); 277 } catch (RemoteException e) { 278 } 279 mWrapper = wrapper; 280 handlePendingMessages(); 281 } 282 283 @Override onServiceDisconnected(ComponentName name)284 public void onServiceDisconnected(ComponentName name) { 285 if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name); 286 handleDeath(); 287 } 288 handlePendingMessages()289 private void handlePendingMessages() { 290 // This ordering is laid out manually to make sure we preserve the TileService 291 // lifecycle. 292 ArraySet<Integer> queue; 293 synchronized (mQueuedMessages) { 294 queue = new ArraySet<>(mQueuedMessages); 295 mQueuedMessages.clear(); 296 } 297 if (queue.contains(MSG_ON_ADDED)) { 298 if (DEBUG) Log.d(TAG, "Handling pending onAdded"); 299 onTileAdded(); 300 } 301 if (mListening) { 302 if (DEBUG) Log.d(TAG, "Handling pending onStartListening"); 303 onStartListening(); 304 } 305 if (queue.contains(MSG_ON_CLICK)) { 306 if (DEBUG) Log.d(TAG, "Handling pending onClick"); 307 if (!mListening) { 308 Log.w(TAG, "Managed to get click on non-listening state..."); 309 // Skipping click since lost click privileges. 310 } else { 311 onClick(mClickBinder); 312 } 313 } 314 if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) { 315 if (DEBUG) Log.d(TAG, "Handling pending onUnlockComplete"); 316 if (!mListening) { 317 Log.w(TAG, "Managed to get unlock on non-listening state..."); 318 // Skipping unlock since lost click privileges. 319 } else { 320 onUnlockComplete(); 321 } 322 } 323 if (queue.contains(MSG_ON_REMOVED)) { 324 if (DEBUG) Log.d(TAG, "Handling pending onRemoved"); 325 if (mListening) { 326 Log.w(TAG, "Managed to get remove in listening state..."); 327 onStopListening(); 328 } 329 onTileRemoved(); 330 } 331 mExecutor.execute(() -> { 332 if (mUnbindImmediate.get()) { 333 mUnbindImmediate.set(false); 334 setBindService(false); 335 } 336 }); 337 } 338 handleDestroy()339 public void handleDestroy() { 340 if (DEBUG) Log.d(TAG, "handleDestroy"); 341 if (mPackageReceiverRegistered.get() || mUserReceiverRegistered.get()) { 342 stopPackageListening(); 343 } 344 mChangeListener = null; 345 } 346 handleDeath()347 private void handleDeath() { 348 if (mWrapper == null) return; 349 freeWrapper(); 350 // Clearly not bound anymore 351 mIsBound.set(false); 352 if (!mBound.get()) return; 353 if (DEBUG) Log.d(TAG, "handleDeath"); 354 if (checkComponentState()) { 355 mExecutor.executeDelayed(() -> setBindService(true), mBindRetryDelay); 356 } 357 } 358 checkComponentState()359 private boolean checkComponentState() { 360 if (!isPackageAvailable() || !isComponentAvailable()) { 361 startPackageListening(); 362 return false; 363 } 364 return true; 365 } 366 startPackageListening()367 private void startPackageListening() { 368 if (DEBUG) Log.d(TAG, "startPackageListening"); 369 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 370 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 371 filter.addDataScheme("package"); 372 try { 373 mPackageReceiverRegistered.set(true); 374 mContext.registerReceiverAsUser( 375 this, mUser, filter, null, mHandler, Context.RECEIVER_EXPORTED); 376 } catch (Exception ex) { 377 mPackageReceiverRegistered.set(false); 378 Log.e(TAG, "Could not register package receiver", ex); 379 } 380 filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); 381 try { 382 mUserReceiverRegistered.set(true); 383 mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler, mUser); 384 } catch (Exception ex) { 385 mUserReceiverRegistered.set(false); 386 Log.e(TAG, "Could not register unlock receiver", ex); 387 } 388 } 389 stopPackageListening()390 private void stopPackageListening() { 391 if (DEBUG) Log.d(TAG, "stopPackageListening"); 392 if (mUserReceiverRegistered.compareAndSet(true, false)) { 393 mBroadcastDispatcher.unregisterReceiver(this); 394 } 395 if (mPackageReceiverRegistered.compareAndSet(true, false)) { 396 mContext.unregisterReceiver(this); 397 } 398 } 399 setTileChangeListener(TileChangeListener changeListener)400 public void setTileChangeListener(TileChangeListener changeListener) { 401 mChangeListener = changeListener; 402 } 403 404 @Override onReceive(Context context, Intent intent)405 public void onReceive(Context context, Intent intent) { 406 if (DEBUG) Log.d(TAG, "onReceive: " + intent); 407 if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { 408 Uri data = intent.getData(); 409 String pkgName = data.getEncodedSchemeSpecificPart(); 410 if (!Objects.equals(pkgName, mIntent.getComponent().getPackageName())) { 411 return; 412 } 413 } 414 if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) && mChangeListener != null) { 415 mChangeListener.onTileChanged(mIntent.getComponent()); 416 } 417 stopPackageListening(); 418 mExecutor.execute(() -> { 419 if (mBound.get()) { 420 // Trying to bind again will check the state of the package before bothering to 421 // bind. 422 if (DEBUG) Log.d(TAG, "Trying to rebind"); 423 setBindService(true); 424 } 425 426 }); 427 } 428 isComponentAvailable()429 private boolean isComponentAvailable() { 430 String packageName = mIntent.getComponent().getPackageName(); 431 try { 432 ServiceInfo si = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), 433 0, mUser.getIdentifier()); 434 if (DEBUG && si == null) Log.d(TAG, "Can't find component " + mIntent.getComponent()); 435 return si != null; 436 } catch (RemoteException e) { 437 // Shouldn't happen. 438 } 439 return false; 440 } 441 isPackageAvailable()442 private boolean isPackageAvailable() { 443 String packageName = mIntent.getComponent().getPackageName(); 444 try { 445 mPackageManagerAdapter.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier()); 446 return true; 447 } catch (PackageManager.NameNotFoundException e) { 448 if (DEBUG) { 449 Log.d(TAG, "Package not available: " + packageName, e); 450 } else { 451 Log.d(TAG, "Package not available: " + packageName); 452 } 453 } 454 return false; 455 } 456 queueMessage(int message)457 private void queueMessage(int message) { 458 synchronized (mQueuedMessages) { 459 mQueuedMessages.add(message); 460 } 461 } 462 463 @Override onTileAdded()464 public void onTileAdded() { 465 if (DEBUG) Log.d(TAG, "onTileAdded"); 466 if (mWrapper == null || !mWrapper.onTileAdded()) { 467 queueMessage(MSG_ON_ADDED); 468 handleDeath(); 469 } 470 } 471 472 @Override onTileRemoved()473 public void onTileRemoved() { 474 if (DEBUG) Log.d(TAG, "onTileRemoved"); 475 if (mWrapper == null || !mWrapper.onTileRemoved()) { 476 queueMessage(MSG_ON_REMOVED); 477 handleDeath(); 478 } 479 } 480 481 @Override onStartListening()482 public void onStartListening() { 483 if (DEBUG) Log.d(TAG, "onStartListening"); 484 mListening = true; 485 if (mWrapper != null && !mWrapper.onStartListening()) { 486 handleDeath(); 487 } 488 } 489 490 @Override onStopListening()491 public void onStopListening() { 492 if (DEBUG) Log.d(TAG, "onStopListening"); 493 mListening = false; 494 if (mWrapper != null && !mWrapper.onStopListening()) { 495 handleDeath(); 496 } 497 } 498 499 @Override onClick(IBinder iBinder)500 public void onClick(IBinder iBinder) { 501 if (DEBUG) Log.d(TAG, "onClick " + iBinder + " " + mUser); 502 if (mWrapper == null || !mWrapper.onClick(iBinder)) { 503 mClickBinder = iBinder; 504 queueMessage(MSG_ON_CLICK); 505 handleDeath(); 506 } 507 } 508 509 @Override onUnlockComplete()510 public void onUnlockComplete() { 511 if (DEBUG) Log.d(TAG, "onUnlockComplete"); 512 if (mWrapper == null || !mWrapper.onUnlockComplete()) { 513 queueMessage(MSG_ON_UNLOCK_COMPLETE); 514 handleDeath(); 515 } 516 } 517 518 @Nullable 519 @Override asBinder()520 public IBinder asBinder() { 521 return mWrapper != null ? mWrapper.asBinder() : null; 522 } 523 524 @Override binderDied()525 public void binderDied() { 526 if (DEBUG) Log.d(TAG, "binderDeath"); 527 handleDeath(); 528 } 529 getToken()530 public IBinder getToken() { 531 return mToken; 532 } 533 freeWrapper()534 private void freeWrapper() { 535 if (mWrapper != null) { 536 try { 537 mWrapper.asBinder().unlinkToDeath(this, 0); 538 } catch (NoSuchElementException e) { 539 Log.w(TAG, "Trying to unlink not linked recipient for component" 540 + mIntent.getComponent().flattenToShortString()); 541 } 542 mWrapper = null; 543 } 544 } 545 546 public interface TileChangeListener { onTileChanged(ComponentName tile)547 void onTileChanged(ComponentName tile); 548 } 549 } 550