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