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