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