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