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