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