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