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