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 java.util.Objects; 42 import 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 */ 51 public 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; 60 private static final int MSG_ON_UNLOCK_COMPLETE = 3; 61 62 // Bind retry control. 63 private static final int MAX_BIND_RETRIES = 5; 64 private static final int DEFAULT_BIND_RETRY_DELAY = 1000; 65 66 // Shared prefs that hold tile lifecycle info. 67 private static final String TILES = "tiles_prefs"; 68 69 private final Context mContext; 70 private final Handler mHandler; 71 private final Intent mIntent; 72 private final UserHandle mUser; 73 private final IBinder mToken = new Binder(); 74 private final PackageManagerAdapter mPackageManagerAdapter; 75 76 private Set<Integer> mQueuedMessages = new ArraySet<>(); 77 private QSTileServiceWrapper mWrapper; 78 private boolean mListening; 79 private IBinder mClickBinder; 80 81 private int mBindTryCount; 82 private int mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY; 83 private boolean mBound; 84 boolean mReceiverRegistered; 85 private boolean mUnbindImmediate; 86 private TileChangeListener mChangeListener; 87 // Return value from bindServiceAsUser, determines whether safe to call unbind. 88 private boolean mIsBound; 89 TileLifecycleManager(Handler handler, Context context, IQSService service, Tile tile, Intent intent, UserHandle user)90 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 TileLifecycleManager(Handler handler, Context context, IQSService service, Tile tile, Intent intent, UserHandle user, PackageManagerAdapter packageManagerAdapter)96 TileLifecycleManager(Handler handler, Context context, IQSService service, Tile tile, 97 Intent intent, UserHandle user, PackageManagerAdapter packageManagerAdapter) { 98 mContext = context; 99 mHandler = handler; 100 mIntent = intent; 101 mIntent.putExtra(TileService.EXTRA_SERVICE, service.asBinder()); 102 mIntent.putExtra(TileService.EXTRA_TOKEN, mToken); 103 mUser = user; 104 mPackageManagerAdapter = packageManagerAdapter; 105 if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser); 106 } 107 getComponent()108 public ComponentName getComponent() { 109 return mIntent.getComponent(); 110 } 111 hasPendingClick()112 public boolean hasPendingClick() { 113 synchronized (mQueuedMessages) { 114 return mQueuedMessages.contains(MSG_ON_CLICK); 115 } 116 } 117 setBindRetryDelay(int delayMs)118 public void setBindRetryDelay(int delayMs) { 119 mBindRetryDelay = delayMs; 120 } 121 isActiveTile()122 public boolean isActiveTile() { 123 try { 124 ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), 125 PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA); 126 return info.metaData != null 127 && info.metaData.getBoolean(TileService.META_DATA_ACTIVE_TILE, false); 128 } catch (PackageManager.NameNotFoundException e) { 129 return false; 130 } 131 } 132 133 /** 134 * Binds just long enough to send any queued messages, then unbinds. 135 */ flushMessagesAndUnbind()136 public void flushMessagesAndUnbind() { 137 mUnbindImmediate = true; 138 setBindService(true); 139 } 140 setBindService(boolean bind)141 public void setBindService(boolean bind) { 142 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 } 148 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 } 158 if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser); 159 mBindTryCount++; 160 try { 161 mIsBound = mContext.bindServiceAsUser(mIntent, this, 162 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE 163 | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, mUser); 164 } catch (SecurityException e) { 165 Log.e(TAG, "Failed to bind to service", e); 166 mIsBound = false; 167 } 168 } else { 169 if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser); 170 // Give it another chance next time it needs to be bound, out of kindness. 171 mBindTryCount = 0; 172 mWrapper = null; 173 if (mIsBound) { 174 mContext.unbindService(this); 175 mIsBound = false; 176 } 177 } 178 } 179 180 @Override onServiceConnected(ComponentName name, IBinder service)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; 185 final QSTileServiceWrapper wrapper = new QSTileServiceWrapper(Stub.asInterface(service)); 186 try { 187 service.linkToDeath(this, 0); 188 } catch (RemoteException e) { 189 } 190 mWrapper = wrapper; 191 handlePendingMessages(); 192 } 193 194 @Override onServiceDisconnected(ComponentName name)195 public void onServiceDisconnected(ComponentName name) { 196 if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name); 197 handleDeath(); 198 } 199 handlePendingMessages()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"); 214 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 } 225 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 } 234 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 } 242 if (mUnbindImmediate) { 243 mUnbindImmediate = false; 244 setBindService(false); 245 } 246 } 247 handleDestroy()248 public void handleDestroy() { 249 if (DEBUG) Log.d(TAG, "handleDestroy"); 250 if (mReceiverRegistered) { 251 stopPackageListening(); 252 } 253 } 254 handleDeath()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 } 269 }, mBindRetryDelay); 270 } 271 } 272 checkComponentState()273 private boolean checkComponentState() { 274 if (!isPackageAvailable() || !isComponentAvailable()) { 275 startPackageListening(); 276 return false; 277 } 278 return true; 279 } 280 startPackageListening()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); 287 filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); 288 mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler); 289 mReceiverRegistered = true; 290 } 291 stopPackageListening()292 private void stopPackageListening() { 293 if (DEBUG) Log.d(TAG, "stopPackageListening"); 294 mContext.unregisterReceiver(this); 295 mReceiverRegistered = false; 296 } 297 setTileChangeListener(TileChangeListener changeListener)298 public void setTileChangeListener(TileChangeListener changeListener) { 299 mChangeListener = changeListener; 300 } 301 302 @Override onReceive(Context context, Intent intent)303 public void onReceive(Context context, Intent intent) { 304 if (DEBUG) Log.d(TAG, "onReceive: " + intent); 305 if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { 306 Uri data = intent.getData(); 307 String pkgName = data.getEncodedSchemeSpecificPart(); 308 if (!Objects.equals(pkgName, mIntent.getComponent().getPackageName())) { 309 return; 310 } 311 } 312 if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) && mChangeListener != null) { 313 mChangeListener.onTileChanged(mIntent.getComponent()); 314 } 315 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 isComponentAvailable()323 private boolean isComponentAvailable() { 324 String packageName = mIntent.getComponent().getPackageName(); 325 try { 326 ServiceInfo si = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), 327 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 isPackageAvailable()336 private boolean isPackageAvailable() { 337 String packageName = mIntent.getComponent().getPackageName(); 338 try { 339 mPackageManagerAdapter.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier()); 340 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 queueMessage(int message)348 private void queueMessage(int message) { 349 synchronized (mQueuedMessages) { 350 mQueuedMessages.add(message); 351 } 352 } 353 354 @Override onTileAdded()355 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 onTileRemoved()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 onStartListening()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 onStopListening()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 onClick(IBinder iBinder)391 public void onClick(IBinder iBinder) { 392 if (DEBUG) Log.d(TAG, "onClick " + iBinder + " " + mUser); 393 if (mWrapper == null || !mWrapper.onClick(iBinder)) { 394 mClickBinder = iBinder; 395 queueMessage(MSG_ON_CLICK); 396 handleDeath(); 397 } 398 } 399 400 @Override onUnlockComplete()401 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 asBinder()410 public IBinder asBinder() { 411 return mWrapper != null ? mWrapper.asBinder() : null; 412 } 413 414 @Override binderDied()415 public void binderDied() { 416 if (DEBUG) Log.d(TAG, "binderDeath"); 417 handleDeath(); 418 } 419 getToken()420 public IBinder getToken() { 421 return mToken; 422 } 423 424 public interface TileChangeListener { onTileChanged(ComponentName tile)425 void onTileChanged(ComponentName tile); 426 } 427 isTileAdded(Context context, ComponentName component)428 public static boolean isTileAdded(Context context, ComponentName component) { 429 return context.getSharedPreferences(TILES, 0).getBoolean(component.flattenToString(), false); 430 } 431 setTileAdded(Context context, ComponentName component, boolean added)432 public static void setTileAdded(Context context, ComponentName component, boolean added) { 433 context.getSharedPreferences(TILES, 0).edit().putBoolean(component.flattenToString(), 434 added).commit(); 435 } 436 } 437