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 163 | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE 164 | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS 165 | Context.BIND_WAIVE_PRIORITY, 166 mUser); 167 } catch (SecurityException e) { 168 Log.e(TAG, "Failed to bind to service", e); 169 mIsBound = false; 170 } 171 } else { 172 if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser); 173 // Give it another chance next time it needs to be bound, out of kindness. 174 mBindTryCount = 0; 175 mWrapper = null; 176 if (mIsBound) { 177 mContext.unbindService(this); 178 mIsBound = false; 179 } 180 } 181 } 182 183 @Override onServiceConnected(ComponentName name, IBinder service)184 public void onServiceConnected(ComponentName name, IBinder service) { 185 if (DEBUG) Log.d(TAG, "onServiceConnected " + name); 186 // Got a connection, set the binding count to 0. 187 mBindTryCount = 0; 188 final QSTileServiceWrapper wrapper = new QSTileServiceWrapper(Stub.asInterface(service)); 189 try { 190 service.linkToDeath(this, 0); 191 } catch (RemoteException e) { 192 } 193 mWrapper = wrapper; 194 handlePendingMessages(); 195 } 196 197 @Override onServiceDisconnected(ComponentName name)198 public void onServiceDisconnected(ComponentName name) { 199 if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name); 200 handleDeath(); 201 } 202 handlePendingMessages()203 private void handlePendingMessages() { 204 // This ordering is laid out manually to make sure we preserve the TileService 205 // lifecycle. 206 ArraySet<Integer> queue; 207 synchronized (mQueuedMessages) { 208 queue = new ArraySet<>(mQueuedMessages); 209 mQueuedMessages.clear(); 210 } 211 if (queue.contains(MSG_ON_ADDED)) { 212 if (DEBUG) Log.d(TAG, "Handling pending onAdded"); 213 onTileAdded(); 214 } 215 if (mListening) { 216 if (DEBUG) Log.d(TAG, "Handling pending onStartListening"); 217 onStartListening(); 218 } 219 if (queue.contains(MSG_ON_CLICK)) { 220 if (DEBUG) Log.d(TAG, "Handling pending onClick"); 221 if (!mListening) { 222 Log.w(TAG, "Managed to get click on non-listening state..."); 223 // Skipping click since lost click privileges. 224 } else { 225 onClick(mClickBinder); 226 } 227 } 228 if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) { 229 if (DEBUG) Log.d(TAG, "Handling pending onUnlockComplete"); 230 if (!mListening) { 231 Log.w(TAG, "Managed to get unlock on non-listening state..."); 232 // Skipping unlock since lost click privileges. 233 } else { 234 onUnlockComplete(); 235 } 236 } 237 if (queue.contains(MSG_ON_REMOVED)) { 238 if (DEBUG) Log.d(TAG, "Handling pending onRemoved"); 239 if (mListening) { 240 Log.w(TAG, "Managed to get remove in listening state..."); 241 onStopListening(); 242 } 243 onTileRemoved(); 244 } 245 if (mUnbindImmediate) { 246 mUnbindImmediate = false; 247 setBindService(false); 248 } 249 } 250 handleDestroy()251 public void handleDestroy() { 252 if (DEBUG) Log.d(TAG, "handleDestroy"); 253 if (mReceiverRegistered) { 254 stopPackageListening(); 255 } 256 } 257 handleDeath()258 private void handleDeath() { 259 if (mWrapper == null) return; 260 mWrapper = null; 261 if (!mBound) return; 262 if (DEBUG) Log.d(TAG, "handleDeath"); 263 if (checkComponentState()) { 264 mHandler.postDelayed(new Runnable() { 265 @Override 266 public void run() { 267 if (mBound) { 268 // Retry binding. 269 setBindService(true); 270 } 271 } 272 }, mBindRetryDelay); 273 } 274 } 275 checkComponentState()276 private boolean checkComponentState() { 277 if (!isPackageAvailable() || !isComponentAvailable()) { 278 startPackageListening(); 279 return false; 280 } 281 return true; 282 } 283 startPackageListening()284 private void startPackageListening() { 285 if (DEBUG) Log.d(TAG, "startPackageListening"); 286 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 287 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 288 filter.addDataScheme("package"); 289 mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler); 290 filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); 291 mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler); 292 mReceiverRegistered = true; 293 } 294 stopPackageListening()295 private void stopPackageListening() { 296 if (DEBUG) Log.d(TAG, "stopPackageListening"); 297 mContext.unregisterReceiver(this); 298 mReceiverRegistered = false; 299 } 300 setTileChangeListener(TileChangeListener changeListener)301 public void setTileChangeListener(TileChangeListener changeListener) { 302 mChangeListener = changeListener; 303 } 304 305 @Override onReceive(Context context, Intent intent)306 public void onReceive(Context context, Intent intent) { 307 if (DEBUG) Log.d(TAG, "onReceive: " + intent); 308 if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { 309 Uri data = intent.getData(); 310 String pkgName = data.getEncodedSchemeSpecificPart(); 311 if (!Objects.equals(pkgName, mIntent.getComponent().getPackageName())) { 312 return; 313 } 314 } 315 if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) && mChangeListener != null) { 316 mChangeListener.onTileChanged(mIntent.getComponent()); 317 } 318 stopPackageListening(); 319 if (mBound) { 320 // Trying to bind again will check the state of the package before bothering to bind. 321 if (DEBUG) Log.d(TAG, "Trying to rebind"); 322 setBindService(true); 323 } 324 } 325 isComponentAvailable()326 private boolean isComponentAvailable() { 327 String packageName = mIntent.getComponent().getPackageName(); 328 try { 329 ServiceInfo si = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), 330 0, mUser.getIdentifier()); 331 if (DEBUG && si == null) Log.d(TAG, "Can't find component " + mIntent.getComponent()); 332 return si != null; 333 } catch (RemoteException e) { 334 // Shouldn't happen. 335 } 336 return false; 337 } 338 isPackageAvailable()339 private boolean isPackageAvailable() { 340 String packageName = mIntent.getComponent().getPackageName(); 341 try { 342 mPackageManagerAdapter.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier()); 343 return true; 344 } catch (PackageManager.NameNotFoundException e) { 345 if (DEBUG) Log.d(TAG, "Package not available: " + packageName, e); 346 else Log.d(TAG, "Package not available: " + packageName); 347 } 348 return false; 349 } 350 queueMessage(int message)351 private void queueMessage(int message) { 352 synchronized (mQueuedMessages) { 353 mQueuedMessages.add(message); 354 } 355 } 356 357 @Override onTileAdded()358 public void onTileAdded() { 359 if (DEBUG) Log.d(TAG, "onTileAdded"); 360 if (mWrapper == null || !mWrapper.onTileAdded()) { 361 queueMessage(MSG_ON_ADDED); 362 handleDeath(); 363 } 364 } 365 366 @Override onTileRemoved()367 public void onTileRemoved() { 368 if (DEBUG) Log.d(TAG, "onTileRemoved"); 369 if (mWrapper == null || !mWrapper.onTileRemoved()) { 370 queueMessage(MSG_ON_REMOVED); 371 handleDeath(); 372 } 373 } 374 375 @Override onStartListening()376 public void onStartListening() { 377 if (DEBUG) Log.d(TAG, "onStartListening"); 378 mListening = true; 379 if (mWrapper != null && !mWrapper.onStartListening()) { 380 handleDeath(); 381 } 382 } 383 384 @Override onStopListening()385 public void onStopListening() { 386 if (DEBUG) Log.d(TAG, "onStopListening"); 387 mListening = false; 388 if (mWrapper != null && !mWrapper.onStopListening()) { 389 handleDeath(); 390 } 391 } 392 393 @Override onClick(IBinder iBinder)394 public void onClick(IBinder iBinder) { 395 if (DEBUG) Log.d(TAG, "onClick " + iBinder + " " + mUser); 396 if (mWrapper == null || !mWrapper.onClick(iBinder)) { 397 mClickBinder = iBinder; 398 queueMessage(MSG_ON_CLICK); 399 handleDeath(); 400 } 401 } 402 403 @Override onUnlockComplete()404 public void onUnlockComplete() { 405 if (DEBUG) Log.d(TAG, "onUnlockComplete"); 406 if (mWrapper == null || !mWrapper.onUnlockComplete()) { 407 queueMessage(MSG_ON_UNLOCK_COMPLETE); 408 handleDeath(); 409 } 410 } 411 412 @Override asBinder()413 public IBinder asBinder() { 414 return mWrapper != null ? mWrapper.asBinder() : null; 415 } 416 417 @Override binderDied()418 public void binderDied() { 419 if (DEBUG) Log.d(TAG, "binderDeath"); 420 handleDeath(); 421 } 422 getToken()423 public IBinder getToken() { 424 return mToken; 425 } 426 427 public interface TileChangeListener { onTileChanged(ComponentName tile)428 void onTileChanged(ComponentName tile); 429 } 430 isTileAdded(Context context, ComponentName component)431 public static boolean isTileAdded(Context context, ComponentName component) { 432 return context.getSharedPreferences(TILES, 0).getBoolean(component.flattenToString(), false); 433 } 434 setTileAdded(Context context, ComponentName component, boolean added)435 public static void setTileAdded(Context context, ComponentName component, boolean added) { 436 context.getSharedPreferences(TILES, 0).edit().putBoolean(component.flattenToString(), 437 added).commit(); 438 } 439 } 440