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 static com.android.systemui.Flags.qsCustomTileClickGuaranteedBugFix; 19 20 import android.app.PendingIntent; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.pm.PackageInfo; 24 import android.content.pm.PackageManager; 25 import android.graphics.drawable.Icon; 26 import android.os.Binder; 27 import android.os.Handler; 28 import android.os.IBinder; 29 import android.os.RemoteException; 30 import android.os.UserHandle; 31 import android.service.quicksettings.IQSService; 32 import android.service.quicksettings.Tile; 33 import android.util.ArrayMap; 34 import android.util.Log; 35 import android.util.SparseArrayMap; 36 37 import androidx.annotation.NonNull; 38 import androidx.annotation.Nullable; 39 import androidx.annotation.VisibleForTesting; 40 41 import com.android.internal.statusbar.StatusBarIcon; 42 import com.android.systemui.broadcast.BroadcastDispatcher; 43 import com.android.systemui.dagger.SysUISingleton; 44 import com.android.systemui.dagger.qualifiers.Background; 45 import com.android.systemui.dagger.qualifiers.Main; 46 import com.android.systemui.qs.QSHost; 47 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository; 48 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; 49 import com.android.systemui.settings.UserTracker; 50 import com.android.systemui.statusbar.CommandQueue; 51 import com.android.systemui.statusbar.phone.ui.StatusBarIconController; 52 import com.android.systemui.statusbar.policy.KeyguardStateController; 53 import com.android.systemui.util.concurrency.DelayableExecutor; 54 55 import java.util.ArrayList; 56 import java.util.Collections; 57 import java.util.Comparator; 58 import java.util.Objects; 59 60 import javax.inject.Inject; 61 import javax.inject.Provider; 62 63 /** 64 * Runs the day-to-day operations of which tiles should be bound and when. 65 */ 66 @SysUISingleton 67 public class TileServices extends IQSService.Stub { 68 static final int DEFAULT_MAX_BOUND = 3; 69 static final int REDUCED_MAX_BOUND = 1; 70 private static final String TAG = "TileServices"; 71 72 private final ArrayMap<CustomTileInterface, TileServiceManager> mServices = new ArrayMap<>(); 73 private final SparseArrayMap<ComponentName, CustomTileInterface> mTiles = 74 new SparseArrayMap<>(); 75 private final ArrayMap<IBinder, CustomTileInterface> mTokenMap = new ArrayMap<>(); 76 private final Context mContext; 77 private final Handler mMainHandler; 78 private final Provider<Handler> mHandlerProvider; 79 private final QSHost mHost; 80 private final KeyguardStateController mKeyguardStateController; 81 private final BroadcastDispatcher mBroadcastDispatcher; 82 private final CommandQueue mCommandQueue; 83 private final UserTracker mUserTracker; 84 private final StatusBarIconController mStatusBarIconController; 85 private final PanelInteractor mPanelInteractor; 86 private final TileLifecycleManager.Factory mTileLifecycleManagerFactory; 87 private final CustomTileAddedRepository mCustomTileAddedRepository; 88 private final DelayableExecutor mBackgroundExecutor; 89 90 private int mMaxBound = DEFAULT_MAX_BOUND; 91 92 @Inject TileServices( QSHost host, @Main Provider<Handler> handlerProvider, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker, KeyguardStateController keyguardStateController, CommandQueue commandQueue, StatusBarIconController statusBarIconController, PanelInteractor panelInteractor, TileLifecycleManager.Factory tileLifecycleManagerFactory, CustomTileAddedRepository customTileAddedRepository, @Background DelayableExecutor backgroundExecutor)93 public TileServices( 94 QSHost host, 95 @Main Provider<Handler> handlerProvider, 96 BroadcastDispatcher broadcastDispatcher, 97 UserTracker userTracker, 98 KeyguardStateController keyguardStateController, 99 CommandQueue commandQueue, 100 StatusBarIconController statusBarIconController, 101 PanelInteractor panelInteractor, 102 TileLifecycleManager.Factory tileLifecycleManagerFactory, 103 CustomTileAddedRepository customTileAddedRepository, 104 @Background DelayableExecutor backgroundExecutor) { 105 mHost = host; 106 mKeyguardStateController = keyguardStateController; 107 mContext = mHost.getContext(); 108 mBroadcastDispatcher = broadcastDispatcher; 109 mHandlerProvider = handlerProvider; 110 mMainHandler = mHandlerProvider.get(); 111 mUserTracker = userTracker; 112 mCommandQueue = commandQueue; 113 mStatusBarIconController = statusBarIconController; 114 mCommandQueue.addCallback(mRequestListeningCallback); 115 mPanelInteractor = panelInteractor; 116 mTileLifecycleManagerFactory = tileLifecycleManagerFactory; 117 mCustomTileAddedRepository = customTileAddedRepository; 118 mBackgroundExecutor = backgroundExecutor; 119 } 120 getContext()121 public Context getContext() { 122 return mContext; 123 } 124 getHost()125 public QSHost getHost() { 126 return mHost; 127 } 128 getTileWrapper(CustomTileInterface tile)129 public TileServiceManager getTileWrapper(CustomTileInterface tile) { 130 ComponentName component = tile.getComponent(); 131 int userId = tile.getUser(); 132 TileServiceManager service = onCreateTileService(component, mBroadcastDispatcher); 133 synchronized (mServices) { 134 mServices.put(tile, service); 135 mTiles.add(userId, component, tile); 136 mTokenMap.put(service.getToken(), tile); 137 } 138 // Makes sure binding only happens after the maps have been populated 139 service.startLifecycleManagerAndAddTile(); 140 return service; 141 } 142 onCreateTileService(ComponentName component, BroadcastDispatcher broadcastDispatcher)143 protected TileServiceManager onCreateTileService(ComponentName component, 144 BroadcastDispatcher broadcastDispatcher) { 145 return new TileServiceManager(this, mHandlerProvider.get(), component, mUserTracker, 146 mTileLifecycleManagerFactory, mCustomTileAddedRepository); 147 } 148 freeService(CustomTileInterface tile, TileServiceManager service)149 public void freeService(CustomTileInterface tile, TileServiceManager service) { 150 synchronized (mServices) { 151 service.setBindAllowed(false); 152 service.handleDestroy(); 153 mServices.remove(tile); 154 mTokenMap.remove(service.getToken()); 155 mTiles.delete(tile.getUser(), tile.getComponent()); 156 final String slot = getStatusBarIconSlotName(tile.getComponent()); 157 mMainHandler.post(() -> mStatusBarIconController.removeIconForTile(slot)); 158 } 159 } 160 setMemoryPressure(boolean memoryPressure)161 public void setMemoryPressure(boolean memoryPressure) { 162 mMaxBound = memoryPressure ? REDUCED_MAX_BOUND : DEFAULT_MAX_BOUND; 163 recalculateBindAllowance(); 164 } 165 recalculateBindAllowance()166 public void recalculateBindAllowance() { 167 final ArrayList<TileServiceManager> services; 168 synchronized (mServices) { 169 services = new ArrayList<>(mServices.values()); 170 } 171 final int N = services.size(); 172 if (N > mMaxBound) { 173 long currentTime = System.currentTimeMillis(); 174 // Precalculate the priority of services for binding. 175 for (int i = 0; i < N; i++) { 176 services.get(i).calculateBindPriority(currentTime); 177 } 178 // Sort them so we can bind the most important first. 179 Collections.sort(services, SERVICE_SORT); 180 } 181 int i; 182 // Allow mMaxBound items to bind. 183 for (i = 0; i < mMaxBound && i < N; i++) { 184 services.get(i).setBindAllowed(true); 185 } 186 // The rest aren't allowed to bind for now. 187 while (i < N) { 188 services.get(i).setBindAllowed(false); 189 i++; 190 } 191 } 192 verifyCaller(CustomTileInterface tile)193 private int verifyCaller(CustomTileInterface tile) { 194 try { 195 String packageName = tile.getComponent().getPackageName(); 196 int uid = mContext.getPackageManager().getPackageUidAsUser(packageName, 197 Binder.getCallingUserHandle().getIdentifier()); 198 if (Binder.getCallingUid() != uid) { 199 throw new SecurityException("Component outside caller's uid"); 200 } 201 return uid; 202 } catch (PackageManager.NameNotFoundException e) { 203 throw new SecurityException(e); 204 } 205 } 206 requestListening(ComponentName component)207 private void requestListening(ComponentName component) { 208 synchronized (mServices) { 209 int userId = mUserTracker.getUserId(); 210 CustomTileInterface customTile = getTileForUserAndComponent(userId, component); 211 if (customTile == null) { 212 Log.d(TAG, "Couldn't find tile for " + component + "(" + userId + ")"); 213 return; 214 } 215 TileServiceManager service = mServices.get(customTile); 216 if (service == null) { 217 Log.e( 218 TAG, 219 "No TileServiceManager found in requestListening for tile " 220 + customTile.getTileSpec()); 221 return; 222 } 223 if (!service.isActiveTile()) { 224 return; 225 } 226 service.setBindRequested(true); 227 if (qsCustomTileClickGuaranteedBugFix()) { 228 service.onStartListeningFromRequest(); 229 } else { 230 try { 231 service.getTileService().onStartListening(); 232 } catch (RemoteException e) { 233 } 234 } 235 } 236 } 237 238 @Override updateQsTile(Tile tile, IBinder token)239 public void updateQsTile(Tile tile, IBinder token) { 240 CustomTileInterface customTile = getTileForToken(token); 241 if (customTile != null) { 242 int uid = verifyCaller(customTile); 243 synchronized (mServices) { 244 final TileServiceManager tileServiceManager = mServices.get(customTile); 245 if (tileServiceManager == null || !tileServiceManager.isLifecycleStarted()) { 246 Log.e(TAG, "TileServiceManager not started for " + customTile.getComponent(), 247 new IllegalStateException()); 248 return; 249 } 250 tileServiceManager.clearPendingBind(); 251 tileServiceManager.setLastUpdate(System.currentTimeMillis()); 252 } 253 customTile.updateTileState(tile, uid); 254 customTile.refreshState(); 255 } 256 } 257 258 @Override onStartSuccessful(IBinder token)259 public void onStartSuccessful(IBinder token) { 260 CustomTileInterface customTile = getTileForToken(token); 261 if (customTile != null) { 262 verifyCaller(customTile); 263 synchronized (mServices) { 264 final TileServiceManager tileServiceManager = mServices.get(customTile); 265 // This should not happen as the TileServiceManager should have been started for the 266 // first bind to happen. 267 if (tileServiceManager == null || !tileServiceManager.isLifecycleStarted()) { 268 Log.e(TAG, "TileServiceManager not started for " + customTile.getComponent(), 269 new IllegalStateException()); 270 return; 271 } 272 tileServiceManager.clearPendingBind(); 273 } 274 customTile.refreshState(); 275 } 276 } 277 278 @Override onShowDialog(IBinder token)279 public void onShowDialog(IBinder token) { 280 CustomTileInterface customTile = getTileForToken(token); 281 if (customTile != null) { 282 verifyCaller(customTile); 283 customTile.onDialogShown(); 284 mPanelInteractor.forceCollapsePanels(); 285 Objects.requireNonNull(mServices.get(customTile)).setShowingDialog(true); 286 } 287 } 288 289 @Override onDialogHidden(IBinder token)290 public void onDialogHidden(IBinder token) { 291 CustomTileInterface customTile = getTileForToken(token); 292 if (customTile != null) { 293 verifyCaller(customTile); 294 Objects.requireNonNull(mServices.get(customTile)).setShowingDialog(false); 295 customTile.onDialogHidden(); 296 } 297 } 298 299 @Override onStartActivity(IBinder token)300 public void onStartActivity(IBinder token) { 301 CustomTileInterface customTile = getTileForToken(token); 302 if (customTile != null) { 303 verifyCaller(customTile); 304 mPanelInteractor.forceCollapsePanels(); 305 } 306 } 307 308 @Override startActivity(IBinder token, PendingIntent pendingIntent)309 public void startActivity(IBinder token, PendingIntent pendingIntent) { 310 startActivity(getTileForToken(token), pendingIntent); 311 } 312 313 @VisibleForTesting startActivity(CustomTileInterface customTile, PendingIntent pendingIntent)314 protected void startActivity(CustomTileInterface customTile, PendingIntent pendingIntent) { 315 if (customTile != null) { 316 verifyCaller(customTile); 317 customTile.startActivityAndCollapse(pendingIntent); 318 } 319 } 320 321 @Override updateStatusIcon(IBinder token, Icon icon, String contentDescription)322 public void updateStatusIcon(IBinder token, Icon icon, String contentDescription) { 323 CustomTileInterface customTile = getTileForToken(token); 324 if (customTile != null) { 325 verifyCaller(customTile); 326 try { 327 ComponentName componentName = customTile.getComponent(); 328 String packageName = componentName.getPackageName(); 329 UserHandle userHandle = getCallingUserHandle(); 330 PackageInfo info = mContext.getPackageManager().getPackageInfoAsUser(packageName, 0, 331 userHandle.getIdentifier()); 332 if (info.applicationInfo.isSystemApp()) { 333 final StatusBarIcon statusIcon = icon != null 334 ? new StatusBarIcon(userHandle, packageName, icon, 0, 0, 335 contentDescription, StatusBarIcon.Type.SystemIcon) 336 : null; 337 final String slot = getStatusBarIconSlotName(componentName); 338 mMainHandler.post(new Runnable() { 339 @Override 340 public void run() { 341 mStatusBarIconController.setIconFromTile(slot, statusIcon); 342 } 343 }); 344 } 345 } catch (PackageManager.NameNotFoundException e) { 346 } 347 } 348 } 349 350 @Nullable 351 @Override getTile(IBinder token)352 public Tile getTile(IBinder token) { 353 CustomTileInterface customTile = getTileForToken(token); 354 if (customTile != null) { 355 verifyCaller(customTile); 356 return customTile.getQsTile(); 357 } 358 Log.e(TAG, "Tile for token " + token + "not found. " 359 + "Tiles in map: " + availableTileComponents()); 360 return null; 361 } 362 availableTileComponents()363 private String availableTileComponents() { 364 StringBuilder sb = new StringBuilder("["); 365 synchronized (mServices) { 366 mTokenMap.forEach((iBinder, customTile) -> 367 sb.append(iBinder.toString()) 368 .append(":") 369 .append(customTile.getComponent().flattenToShortString()) 370 .append(":") 371 .append(customTile.getUser()) 372 .append(",")); 373 } 374 sb.append("]"); 375 return sb.toString(); 376 } 377 378 @Override startUnlockAndRun(IBinder token)379 public void startUnlockAndRun(IBinder token) { 380 CustomTileInterface customTile = getTileForToken(token); 381 if (customTile != null) { 382 verifyCaller(customTile); 383 customTile.startUnlockAndRun(); 384 } 385 } 386 387 @Override isLocked()388 public boolean isLocked() { 389 return mKeyguardStateController.isShowing(); 390 } 391 392 @Override isSecure()393 public boolean isSecure() { 394 return mKeyguardStateController.isMethodSecure() && mKeyguardStateController.isShowing(); 395 } 396 397 @Nullable getTileForToken(IBinder token)398 public CustomTileInterface getTileForToken(IBinder token) { 399 synchronized (mServices) { 400 return mTokenMap.get(token); 401 } 402 } 403 404 @Nullable getTileForUserAndComponent(int userId, ComponentName component)405 private CustomTileInterface getTileForUserAndComponent(int userId, ComponentName component) { 406 synchronized (mServices) { 407 return mTiles.get(userId, component); 408 } 409 } 410 destroy()411 public void destroy() { 412 synchronized (mServices) { 413 mServices.values().forEach(service -> service.handleDestroy()); 414 } 415 mCommandQueue.removeCallback(mRequestListeningCallback); 416 } 417 418 /** Returns the slot name that should be used when adding or removing status bar icons. */ getStatusBarIconSlotName(ComponentName componentName)419 private String getStatusBarIconSlotName(ComponentName componentName) { 420 return componentName.getClassName(); 421 } 422 423 424 private final CommandQueue.Callbacks mRequestListeningCallback = new CommandQueue.Callbacks() { 425 @Override 426 public void requestTileServiceListeningState(@NonNull ComponentName componentName) { 427 mMainHandler.post(() -> requestListening(componentName)); 428 } 429 }; 430 431 private static final Comparator<TileServiceManager> SERVICE_SORT = 432 (left, right) -> -Integer.compare(left.getBindPriority(), right.getBindPriority()); 433 434 } 435