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