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