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.IQSTileService; 34 import android.service.quicksettings.Tile; 35 import android.service.quicksettings.TileService; 36 import android.util.ArrayMap; 37 import android.util.Log; 38 39 import com.android.internal.statusbar.StatusBarIcon; 40 import com.android.systemui.statusbar.phone.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 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 74 public Context getContext() { 75 return mContext; 76 } 77 78 public QSTileHost getHost() { 79 return mHost; 80 } 81 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 93 protected TileServiceManager onCreateTileService(ComponentName component, Tile tile) { 94 return new TileServiceManager(this, mHandler, component, tile); 95 } 96 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 mMainHandler.post(new Runnable() { 106 @Override 107 public void run() { 108 mHost.getIconController().removeIcon(slot); 109 } 110 }); 111 } 112 } 113 114 public void setMemoryPressure(boolean memoryPressure) { 115 mMaxBound = memoryPressure ? REDUCED_MAX_BOUND : DEFAULT_MAX_BOUND; 116 recalculateBindAllowance(); 117 } 118 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 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 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 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 tileServiceManager.clearPendingBind(); 186 tileServiceManager.setLastUpdate(System.currentTimeMillis()); 187 } 188 customTile.updateState(tile); 189 customTile.refreshState(); 190 } 191 } 192 193 @Override 194 public void onStartSuccessful(IBinder token) { 195 CustomTile customTile = getTileForToken(token); 196 if (customTile != null) { 197 verifyCaller(customTile); 198 synchronized (mServices) { 199 final TileServiceManager tileServiceManager = mServices.get(customTile); 200 tileServiceManager.clearPendingBind(); 201 } 202 customTile.refreshState(); 203 } 204 } 205 206 @Override 207 public void onShowDialog(IBinder token) { 208 CustomTile customTile = getTileForToken(token); 209 if (customTile != null) { 210 verifyCaller(customTile); 211 customTile.onDialogShown(); 212 mHost.collapsePanels(); 213 mServices.get(customTile).setShowingDialog(true); 214 } 215 } 216 217 @Override 218 public void onDialogHidden(IBinder token) { 219 CustomTile customTile = getTileForToken(token); 220 if (customTile != null) { 221 verifyCaller(customTile); 222 mServices.get(customTile).setShowingDialog(false); 223 customTile.onDialogHidden(); 224 } 225 } 226 227 @Override 228 public void onStartActivity(IBinder token) { 229 CustomTile customTile = getTileForToken(token); 230 if (customTile != null) { 231 verifyCaller(customTile); 232 mHost.collapsePanels(); 233 } 234 } 235 236 @Override 237 public void updateStatusIcon(IBinder token, Icon icon, String contentDescription) { 238 CustomTile customTile = getTileForToken(token); 239 if (customTile != null) { 240 verifyCaller(customTile); 241 try { 242 ComponentName componentName = customTile.getComponent(); 243 String packageName = componentName.getPackageName(); 244 UserHandle userHandle = getCallingUserHandle(); 245 PackageInfo info = mContext.getPackageManager().getPackageInfoAsUser(packageName, 0, 246 userHandle.getIdentifier()); 247 if (info.applicationInfo.isSystemApp()) { 248 final StatusBarIcon statusIcon = icon != null 249 ? new StatusBarIcon(userHandle, packageName, icon, 0, 0, 250 contentDescription) 251 : null; 252 mMainHandler.post(new Runnable() { 253 @Override 254 public void run() { 255 StatusBarIconController iconController = mHost.getIconController(); 256 iconController.setIcon(componentName.getClassName(), statusIcon); 257 iconController.setExternalIcon(componentName.getClassName()); 258 } 259 }); 260 } 261 } catch (PackageManager.NameNotFoundException e) { 262 } 263 } 264 } 265 266 @Override 267 public Tile getTile(IBinder token) { 268 CustomTile customTile = getTileForToken(token); 269 if (customTile != null) { 270 verifyCaller(customTile); 271 return customTile.getQsTile(); 272 } 273 return null; 274 } 275 276 @Override 277 public void startUnlockAndRun(IBinder token) { 278 CustomTile customTile = getTileForToken(token); 279 if (customTile != null) { 280 verifyCaller(customTile); 281 customTile.startUnlockAndRun(); 282 } 283 } 284 285 @Override 286 public boolean isLocked() { 287 KeyguardMonitor keyguardMonitor = mHost.getKeyguardMonitor(); 288 return keyguardMonitor.isShowing(); 289 } 290 291 @Override 292 public boolean isSecure() { 293 KeyguardMonitor keyguardMonitor = mHost.getKeyguardMonitor(); 294 return keyguardMonitor.isSecure() && keyguardMonitor.isShowing(); 295 } 296 297 private CustomTile getTileForToken(IBinder token) { 298 synchronized (mServices) { 299 return mTokenMap.get(token); 300 } 301 } 302 303 private CustomTile getTileForComponent(ComponentName component) { 304 synchronized (mServices) { 305 return mTiles.get(component); 306 } 307 } 308 309 private final BroadcastReceiver mRequestListeningReceiver = new BroadcastReceiver() { 310 @Override 311 public void onReceive(Context context, Intent intent) { 312 if (TileService.ACTION_REQUEST_LISTENING.equals(intent.getAction())) { 313 requestListening( 314 (ComponentName) intent.getParcelableExtra(TileService.EXTRA_COMPONENT)); 315 } 316 } 317 }; 318 319 private static final Comparator<TileServiceManager> SERVICE_SORT = 320 new Comparator<TileServiceManager>() { 321 @Override 322 public int compare(TileServiceManager left, TileServiceManager right) { 323 return -Integer.compare(left.getBindPriority(), right.getBindPriority()); 324 } 325 }; 326 } 327