TileLifecycleManager.java revision 604c2f9440743f72f136505b2b6afe6f2aed9072
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 */ 16package com.android.systemui.qs.external; 17 18import android.app.AppGlobals; 19import android.content.BroadcastReceiver; 20import android.content.ComponentName; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.content.ServiceConnection; 25import android.content.pm.PackageManager; 26import android.content.pm.PackageManager.NameNotFoundException; 27import android.content.pm.ServiceInfo; 28import android.net.Uri; 29import android.os.Handler; 30import android.os.IBinder; 31import android.os.RemoteException; 32import android.os.UserHandle; 33import android.service.quicksettings.IQSService; 34import android.service.quicksettings.IQSTileService; 35import android.service.quicksettings.Tile; 36import android.service.quicksettings.TileService; 37import android.support.annotation.VisibleForTesting; 38import android.util.ArraySet; 39import android.util.Log; 40import libcore.util.Objects; 41 42import 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 */ 51public 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 BIND_RETRY_DELAY = 1000; 65 66 private final Context mContext; 67 private final Handler mHandler; 68 private final Intent mIntent; 69 private final UserHandle mUser; 70 71 private Set<Integer> mQueuedMessages = new ArraySet<>(); 72 private QSTileServiceWrapper mWrapper; 73 private boolean mListening; 74 private Tile mTile; 75 private IBinder mClickBinder; 76 77 private int mBindTryCount; 78 private boolean mBound; 79 @VisibleForTesting 80 boolean mReceiverRegistered; 81 private IQSService mService; 82 private boolean mUnbindImmediate; 83 private TileChangeListener mChangeListener; 84 // Return value from bindServiceAsUser, determines whether safe to call unbind. 85 private boolean mIsBound; 86 87 public TileLifecycleManager(Handler handler, Context context, Intent intent, UserHandle user) { 88 mContext = context; 89 mHandler = handler; 90 mIntent = intent; 91 mUser = user; 92 if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser); 93 } 94 95 public ComponentName getComponent() { 96 return mIntent.getComponent(); 97 } 98 99 public boolean hasPendingClick() { 100 synchronized (mQueuedMessages) { 101 return mQueuedMessages.contains(MSG_ON_CLICK); 102 } 103 } 104 105 public boolean isActiveTile() { 106 try { 107 ServiceInfo info = mContext.getPackageManager().getServiceInfo(mIntent.getComponent(), 108 PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA); 109 return info.metaData != null 110 && info.metaData.getBoolean(TileService.META_DATA_ACTIVE_TILE, false); 111 } catch (NameNotFoundException e) { 112 return false; 113 } 114 } 115 116 /** 117 * Binds just long enough to send any queued messages, then unbinds. 118 */ 119 public void flushMessagesAndUnbind() { 120 mUnbindImmediate = true; 121 setBindService(true); 122 } 123 124 public void setBindService(boolean bind) { 125 mBound = bind; 126 if (bind) { 127 if (mBindTryCount == MAX_BIND_RETRIES) { 128 // Too many failures, give up on this tile until an update. 129 startPackageListening(); 130 return; 131 } 132 if (!checkComponentState()) { 133 return; 134 } 135 if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser); 136 mBindTryCount++; 137 try { 138 mIsBound = mContext.bindServiceAsUser(mIntent, this, 139 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, 140 mUser); 141 } catch (SecurityException e) { 142 Log.e(TAG, "Failed to bind to service", e); 143 mIsBound = false; 144 } 145 } else { 146 if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser); 147 // Give it another chance next time it needs to be bound, out of kindness. 148 mBindTryCount = 0; 149 mWrapper = null; 150 if (mIsBound) { 151 mContext.unbindService(this); 152 mIsBound = false; 153 } 154 } 155 } 156 157 @Override 158 public void onServiceConnected(ComponentName name, IBinder service) { 159 if (DEBUG) Log.d(TAG, "onServiceConnected " + name); 160 // Got a connection, set the binding count to 0. 161 mBindTryCount = 0; 162 final QSTileServiceWrapper wrapper = new QSTileServiceWrapper(Stub.asInterface(service)); 163 try { 164 service.linkToDeath(this, 0); 165 } catch (RemoteException e) { 166 } 167 if (!wrapper.setQSService(mService)) { 168 handleDeath(); 169 return; 170 } 171 if (!wrapper.setQSTile(mTile)) { 172 handleDeath(); 173 return; 174 } 175 mWrapper = wrapper; 176 handlePendingMessages(); 177 } 178 179 @Override 180 public void onServiceDisconnected(ComponentName name) { 181 if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name); 182 handleDeath(); 183 } 184 185 private void handlePendingMessages() { 186 // This ordering is laid out manually to make sure we preserve the TileService 187 // lifecycle. 188 ArraySet<Integer> queue; 189 synchronized (mQueuedMessages) { 190 queue = new ArraySet<>(mQueuedMessages); 191 mQueuedMessages.clear(); 192 } 193 if (queue.contains(MSG_ON_ADDED)) { 194 if (DEBUG) Log.d(TAG, "Handling pending onAdded"); 195 onTileAdded(); 196 } 197 if (mListening) { 198 if (DEBUG) Log.d(TAG, "Handling pending onStartListening"); 199 onStartListening(); 200 } 201 if (queue.contains(MSG_ON_CLICK)) { 202 if (DEBUG) Log.d(TAG, "Handling pending onClick"); 203 if (!mListening) { 204 Log.w(TAG, "Managed to get click on non-listening state..."); 205 // Skipping click since lost click privileges. 206 } else { 207 onClick(mClickBinder); 208 } 209 } 210 if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) { 211 if (DEBUG) Log.d(TAG, "Handling pending onUnlockComplete"); 212 if (!mListening) { 213 Log.w(TAG, "Managed to get unlock on non-listening state..."); 214 // Skipping unlock since lost click privileges. 215 } else { 216 onUnlockComplete(); 217 } 218 } 219 if (queue.contains(MSG_ON_REMOVED)) { 220 if (DEBUG) Log.d(TAG, "Handling pending onRemoved"); 221 if (mListening) { 222 Log.w(TAG, "Managed to get remove in listening state..."); 223 onStopListening(); 224 } 225 onTileRemoved(); 226 } 227 if (mUnbindImmediate) { 228 mUnbindImmediate = false; 229 setBindService(false); 230 } 231 } 232 233 public void handleDestroy() { 234 if (DEBUG) Log.d(TAG, "handleDestroy"); 235 if (mReceiverRegistered) { 236 stopPackageListening(); 237 } 238 } 239 240 private void handleDeath() { 241 if (mWrapper == null) return; 242 mWrapper = null; 243 if (!mBound) return; 244 if (DEBUG) Log.d(TAG, "handleDeath"); 245 if (checkComponentState()) { 246 mHandler.postDelayed(new Runnable() { 247 @Override 248 public void run() { 249 if (mBound) { 250 // Retry binding. 251 setBindService(true); 252 } 253 } 254 }, BIND_RETRY_DELAY); 255 } 256 } 257 258 @Override 259 public void setQSTile(Tile tile) { 260 if (DEBUG) Log.d(TAG, "setQSTile " + tile); 261 mTile = tile; 262 if (mWrapper != null && !mWrapper.setQSTile(tile)) { 263 handleDeath(); 264 } 265 } 266 267 private boolean checkComponentState() { 268 PackageManager pm = mContext.getPackageManager(); 269 if (!isPackageAvailable(pm) || !isComponentAvailable(pm)) { 270 startPackageListening(); 271 return false; 272 } 273 return true; 274 } 275 276 private void startPackageListening() { 277 if (DEBUG) Log.d(TAG, "startPackageListening"); 278 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 279 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 280 filter.addDataScheme("package"); 281 mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler); 282 filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); 283 mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler); 284 mReceiverRegistered = true; 285 } 286 287 private void stopPackageListening() { 288 if (DEBUG) Log.d(TAG, "stopPackageListening"); 289 mContext.unregisterReceiver(this); 290 mReceiverRegistered = false; 291 } 292 293 public void setTileChangeListener(TileChangeListener changeListener) { 294 mChangeListener = changeListener; 295 } 296 297 @Override 298 public void onReceive(Context context, Intent intent) { 299 if (DEBUG) Log.d(TAG, "onReceive: " + intent); 300 if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { 301 Uri data = intent.getData(); 302 String pkgName = data.getEncodedSchemeSpecificPart(); 303 if (!Objects.equal(pkgName, mIntent.getComponent().getPackageName())) { 304 return; 305 } 306 } 307 if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) && mChangeListener != null) { 308 mChangeListener.onTileChanged(mIntent.getComponent()); 309 } 310 stopPackageListening(); 311 if (mBound) { 312 // Trying to bind again will check the state of the package before bothering to bind. 313 if (DEBUG) Log.d(TAG, "Trying to rebind"); 314 setBindService(true); 315 } 316 } 317 318 private boolean isComponentAvailable(PackageManager pm) { 319 String packageName = mIntent.getComponent().getPackageName(); 320 try { 321 ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(mIntent.getComponent(), 322 0, mUser.getIdentifier()); 323 if (DEBUG && si == null) Log.d(TAG, "Can't find component " + mIntent.getComponent()); 324 return si != null; 325 } catch (RemoteException e) { 326 // Shouldn't happen. 327 } 328 return false; 329 } 330 331 private boolean isPackageAvailable(PackageManager pm) { 332 String packageName = mIntent.getComponent().getPackageName(); 333 try { 334 pm.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier()); 335 return true; 336 } catch (PackageManager.NameNotFoundException e) { 337 if (DEBUG) Log.d(TAG, "Package not available: " + packageName, e); 338 else Log.d(TAG, "Package not available: " + packageName); 339 } 340 return false; 341 } 342 343 private void queueMessage(int message) { 344 synchronized (mQueuedMessages) { 345 mQueuedMessages.add(message); 346 } 347 } 348 349 @Override 350 public void setQSService(IQSService service) { 351 mService = service; 352 if (mWrapper == null || !mWrapper.setQSService(service)) { 353 handleDeath(); 354 } 355 } 356 357 @Override 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 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 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 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 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 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 413 public IBinder asBinder() { 414 return mWrapper != null ? mWrapper.asBinder() : null; 415 } 416 417 @Override 418 public void binderDied() { 419 if (DEBUG) Log.d(TAG, "binderDeath"); 420 handleDeath(); 421 } 422 423 public interface TileChangeListener { 424 void onTileChanged(ComponentName tile); 425 } 426} 427