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.content.BroadcastReceiver; 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 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.Looper; 30 import android.os.RemoteException; 31 import android.os.UserHandle; 32 import android.service.quicksettings.IQSService; 33 import android.service.quicksettings.Tile; 34 import android.service.quicksettings.TileService; 35 import android.util.ArrayMap; 36 import android.util.Log; 37 38 import com.android.internal.statusbar.StatusBarIcon; 39 import com.android.systemui.Dependency; 40 import com.android.systemui.broadcast.BroadcastDispatcher; 41 import com.android.systemui.qs.QSTileHost; 42 import com.android.systemui.settings.UserTracker; 43 import com.android.systemui.statusbar.phone.StatusBarIconController; 44 import com.android.systemui.statusbar.policy.KeyguardStateController; 45 46 import java.util.ArrayList; 47 import java.util.Collections; 48 import java.util.Comparator; 49 50 /** 51 * Runs the day-to-day operations of which tiles should be bound and when. 52 */ 53 public class TileServices extends IQSService.Stub { 54 static final int DEFAULT_MAX_BOUND = 3; 55 static final int REDUCED_MAX_BOUND = 1; 56 private static final String TAG = "TileServices"; 57 58 private final ArrayMap<CustomTile, TileServiceManager> mServices = new ArrayMap<>(); 59 private final ArrayMap<ComponentName, CustomTile> mTiles = new ArrayMap<>(); 60 private final ArrayMap<IBinder, CustomTile> mTokenMap = new ArrayMap<>(); 61 private final Context mContext; 62 private final Handler mHandler; 63 private final Handler mMainHandler; 64 private final QSTileHost mHost; 65 private final BroadcastDispatcher mBroadcastDispatcher; 66 private final UserTracker mUserTracker; 67 68 private int mMaxBound = DEFAULT_MAX_BOUND; 69 TileServices(QSTileHost host, Looper looper, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker)70 public TileServices(QSTileHost host, Looper looper, BroadcastDispatcher broadcastDispatcher, 71 UserTracker userTracker) { 72 mHost = host; 73 mContext = mHost.getContext(); 74 mBroadcastDispatcher = broadcastDispatcher; 75 mHandler = new Handler(looper); 76 mMainHandler = new Handler(Looper.getMainLooper()); 77 mUserTracker = userTracker; 78 mBroadcastDispatcher.registerReceiver( 79 mRequestListeningReceiver, 80 new IntentFilter(TileService.ACTION_REQUEST_LISTENING), 81 null, // Use the default Executor 82 UserHandle.ALL 83 ); 84 } 85 getContext()86 public Context getContext() { 87 return mContext; 88 } 89 getHost()90 public QSTileHost getHost() { 91 return mHost; 92 } 93 getTileWrapper(CustomTile tile)94 public TileServiceManager getTileWrapper(CustomTile tile) { 95 ComponentName component = tile.getComponent(); 96 TileServiceManager service = onCreateTileService(component, tile.getQsTile(), 97 mBroadcastDispatcher); 98 synchronized (mServices) { 99 mServices.put(tile, service); 100 mTiles.put(component, tile); 101 mTokenMap.put(service.getToken(), tile); 102 } 103 // Makes sure binding only happens after the maps have been populated 104 service.startLifecycleManagerAndAddTile(); 105 return service; 106 } 107 onCreateTileService(ComponentName component, Tile tile, BroadcastDispatcher broadcastDispatcher)108 protected TileServiceManager onCreateTileService(ComponentName component, Tile tile, 109 BroadcastDispatcher broadcastDispatcher) { 110 return new TileServiceManager(this, mHandler, component, tile, 111 broadcastDispatcher, mUserTracker); 112 } 113 freeService(CustomTile tile, TileServiceManager service)114 public void freeService(CustomTile tile, TileServiceManager service) { 115 synchronized (mServices) { 116 service.setBindAllowed(false); 117 service.handleDestroy(); 118 mServices.remove(tile); 119 mTokenMap.remove(service.getToken()); 120 mTiles.remove(tile.getComponent()); 121 final String slot = tile.getComponent().getClassName(); 122 // TileServices doesn't know how to add more than 1 icon per slot, so remove all 123 mMainHandler.post(() -> mHost.getIconController() 124 .removeAllIconsForSlot(slot)); 125 } 126 } 127 setMemoryPressure(boolean memoryPressure)128 public void setMemoryPressure(boolean memoryPressure) { 129 mMaxBound = memoryPressure ? REDUCED_MAX_BOUND : DEFAULT_MAX_BOUND; 130 recalculateBindAllowance(); 131 } 132 recalculateBindAllowance()133 public void recalculateBindAllowance() { 134 final ArrayList<TileServiceManager> services; 135 synchronized (mServices) { 136 services = new ArrayList<>(mServices.values()); 137 } 138 final int N = services.size(); 139 if (N > mMaxBound) { 140 long currentTime = System.currentTimeMillis(); 141 // Precalculate the priority of services for binding. 142 for (int i = 0; i < N; i++) { 143 services.get(i).calculateBindPriority(currentTime); 144 } 145 // Sort them so we can bind the most important first. 146 Collections.sort(services, SERVICE_SORT); 147 } 148 int i; 149 // Allow mMaxBound items to bind. 150 for (i = 0; i < mMaxBound && i < N; i++) { 151 services.get(i).setBindAllowed(true); 152 } 153 // The rest aren't allowed to bind for now. 154 while (i < N) { 155 services.get(i).setBindAllowed(false); 156 i++; 157 } 158 } 159 verifyCaller(CustomTile tile)160 private void verifyCaller(CustomTile tile) { 161 try { 162 String packageName = tile.getComponent().getPackageName(); 163 int uid = mContext.getPackageManager().getPackageUidAsUser(packageName, 164 Binder.getCallingUserHandle().getIdentifier()); 165 if (Binder.getCallingUid() != uid) { 166 throw new SecurityException("Component outside caller's uid"); 167 } 168 } catch (PackageManager.NameNotFoundException e) { 169 throw new SecurityException(e); 170 } 171 } 172 requestListening(ComponentName component)173 private void requestListening(ComponentName component) { 174 synchronized (mServices) { 175 CustomTile customTile = getTileForComponent(component); 176 if (customTile == null) { 177 Log.d("TileServices", "Couldn't find tile for " + component); 178 return; 179 } 180 TileServiceManager service = mServices.get(customTile); 181 if (!service.isActiveTile()) { 182 return; 183 } 184 service.setBindRequested(true); 185 try { 186 service.getTileService().onStartListening(); 187 } catch (RemoteException e) { 188 } 189 } 190 } 191 192 @Override updateQsTile(Tile tile, IBinder token)193 public void updateQsTile(Tile tile, IBinder token) { 194 CustomTile customTile = getTileForToken(token); 195 if (customTile != null) { 196 verifyCaller(customTile); 197 synchronized (mServices) { 198 final TileServiceManager tileServiceManager = mServices.get(customTile); 199 if (tileServiceManager == null || !tileServiceManager.isLifecycleStarted()) { 200 Log.e(TAG, "TileServiceManager not started for " + customTile.getComponent(), 201 new IllegalStateException()); 202 return; 203 } 204 tileServiceManager.clearPendingBind(); 205 tileServiceManager.setLastUpdate(System.currentTimeMillis()); 206 } 207 customTile.updateTileState(tile); 208 customTile.refreshState(); 209 } 210 } 211 212 @Override onStartSuccessful(IBinder token)213 public void onStartSuccessful(IBinder token) { 214 CustomTile customTile = getTileForToken(token); 215 if (customTile != null) { 216 verifyCaller(customTile); 217 synchronized (mServices) { 218 final TileServiceManager tileServiceManager = mServices.get(customTile); 219 // This should not happen as the TileServiceManager should have been started for the 220 // first bind to happen. 221 if (tileServiceManager == null || !tileServiceManager.isLifecycleStarted()) { 222 Log.e(TAG, "TileServiceManager not started for " + customTile.getComponent(), 223 new IllegalStateException()); 224 return; 225 } 226 tileServiceManager.clearPendingBind(); 227 } 228 customTile.refreshState(); 229 } 230 } 231 232 @Override onShowDialog(IBinder token)233 public void onShowDialog(IBinder token) { 234 CustomTile customTile = getTileForToken(token); 235 if (customTile != null) { 236 verifyCaller(customTile); 237 customTile.onDialogShown(); 238 mHost.forceCollapsePanels(); 239 mServices.get(customTile).setShowingDialog(true); 240 } 241 } 242 243 @Override onDialogHidden(IBinder token)244 public void onDialogHidden(IBinder token) { 245 CustomTile customTile = getTileForToken(token); 246 if (customTile != null) { 247 verifyCaller(customTile); 248 mServices.get(customTile).setShowingDialog(false); 249 customTile.onDialogHidden(); 250 } 251 } 252 253 @Override onStartActivity(IBinder token)254 public void onStartActivity(IBinder token) { 255 CustomTile customTile = getTileForToken(token); 256 if (customTile != null) { 257 verifyCaller(customTile); 258 mHost.forceCollapsePanels(); 259 } 260 } 261 262 @Override updateStatusIcon(IBinder token, Icon icon, String contentDescription)263 public void updateStatusIcon(IBinder token, Icon icon, String contentDescription) { 264 CustomTile customTile = getTileForToken(token); 265 if (customTile != null) { 266 verifyCaller(customTile); 267 try { 268 ComponentName componentName = customTile.getComponent(); 269 String packageName = componentName.getPackageName(); 270 UserHandle userHandle = getCallingUserHandle(); 271 PackageInfo info = mContext.getPackageManager().getPackageInfoAsUser(packageName, 0, 272 userHandle.getIdentifier()); 273 if (info.applicationInfo.isSystemApp()) { 274 final StatusBarIcon statusIcon = icon != null 275 ? new StatusBarIcon(userHandle, packageName, icon, 0, 0, 276 contentDescription) 277 : null; 278 mMainHandler.post(new Runnable() { 279 @Override 280 public void run() { 281 StatusBarIconController iconController = mHost.getIconController(); 282 iconController.setIcon(componentName.getClassName(), statusIcon); 283 iconController.setExternalIcon(componentName.getClassName()); 284 } 285 }); 286 } 287 } catch (PackageManager.NameNotFoundException e) { 288 } 289 } 290 } 291 292 @Override getTile(IBinder token)293 public Tile getTile(IBinder token) { 294 CustomTile customTile = getTileForToken(token); 295 if (customTile != null) { 296 verifyCaller(customTile); 297 return customTile.getQsTile(); 298 } 299 return null; 300 } 301 302 @Override startUnlockAndRun(IBinder token)303 public void startUnlockAndRun(IBinder token) { 304 CustomTile customTile = getTileForToken(token); 305 if (customTile != null) { 306 verifyCaller(customTile); 307 customTile.startUnlockAndRun(); 308 } 309 } 310 311 @Override isLocked()312 public boolean isLocked() { 313 KeyguardStateController keyguardStateController = 314 Dependency.get(KeyguardStateController.class); 315 return keyguardStateController.isShowing(); 316 } 317 318 @Override isSecure()319 public boolean isSecure() { 320 KeyguardStateController keyguardStateController = 321 Dependency.get(KeyguardStateController.class); 322 return keyguardStateController.isMethodSecure() && keyguardStateController.isShowing(); 323 } 324 getTileForToken(IBinder token)325 private CustomTile getTileForToken(IBinder token) { 326 synchronized (mServices) { 327 return mTokenMap.get(token); 328 } 329 } 330 getTileForComponent(ComponentName component)331 private CustomTile getTileForComponent(ComponentName component) { 332 synchronized (mServices) { 333 return mTiles.get(component); 334 } 335 } 336 destroy()337 public void destroy() { 338 synchronized (mServices) { 339 mServices.values().forEach(service -> service.handleDestroy()); 340 mBroadcastDispatcher.unregisterReceiver(mRequestListeningReceiver); 341 } 342 } 343 344 private final BroadcastReceiver mRequestListeningReceiver = new BroadcastReceiver() { 345 @Override 346 public void onReceive(Context context, Intent intent) { 347 if (TileService.ACTION_REQUEST_LISTENING.equals(intent.getAction())) { 348 requestListening( 349 (ComponentName) intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME)); 350 } 351 } 352 }; 353 354 private static final Comparator<TileServiceManager> SERVICE_SORT = 355 new Comparator<TileServiceManager>() { 356 @Override 357 public int compare(TileServiceManager left, TileServiceManager right) { 358 return -Integer.compare(left.getBindPriority(), right.getBindPriority()); 359 } 360 }; 361 } 362