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