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