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 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(() -> mHost.getIconController().removeIcon(slot)); 106 } 107 } 108 109 public void setMemoryPressure(boolean memoryPressure) { 110 mMaxBound = memoryPressure ? REDUCED_MAX_BOUND : DEFAULT_MAX_BOUND; 111 recalculateBindAllowance(); 112 } 113 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 141 private void verifyCaller(CustomTile tile) { 142 try { 143 String packageName = tile.getComponent().getPackageName(); 144 int uid = mContext.getPackageManager().getPackageUidAsUser(packageName, 145 Binder.getCallingUserHandle().getIdentifier()); 146 if (Binder.getCallingUid() != uid) { 147 throw new SecurityException("Component outside caller's uid"); 148 } 149 } catch (PackageManager.NameNotFoundException e) { 150 throw new SecurityException(e); 151 } 152 } 153 154 private void requestListening(ComponentName component) { 155 synchronized (mServices) { 156 CustomTile customTile = getTileForComponent(component); 157 if (customTile == null) { 158 Log.d("TileServices", "Couldn't find tile for " + component); 159 return; 160 } 161 TileServiceManager service = mServices.get(customTile); 162 if (!service.isActiveTile()) { 163 return; 164 } 165 service.setBindRequested(true); 166 try { 167 service.getTileService().onStartListening(); 168 } catch (RemoteException e) { 169 } 170 } 171 } 172 173 @Override 174 public void updateQsTile(Tile tile, IBinder token) { 175 CustomTile customTile = getTileForToken(token); 176 if (customTile != null) { 177 verifyCaller(customTile); 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 189 public void onStartSuccessful(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 tileServiceManager.clearPendingBind(); 196 } 197 customTile.refreshState(); 198 } 199 } 200 201 @Override 202 public void onShowDialog(IBinder token) { 203 CustomTile customTile = getTileForToken(token); 204 if (customTile != null) { 205 verifyCaller(customTile); 206 customTile.onDialogShown(); 207 mHost.forceCollapsePanels(); 208 mServices.get(customTile).setShowingDialog(true); 209 } 210 } 211 212 @Override 213 public void onDialogHidden(IBinder token) { 214 CustomTile customTile = getTileForToken(token); 215 if (customTile != null) { 216 verifyCaller(customTile); 217 mServices.get(customTile).setShowingDialog(false); 218 customTile.onDialogHidden(); 219 } 220 } 221 222 @Override 223 public void onStartActivity(IBinder token) { 224 CustomTile customTile = getTileForToken(token); 225 if (customTile != null) { 226 verifyCaller(customTile); 227 mHost.forceCollapsePanels(); 228 } 229 } 230 231 @Override 232 public void updateStatusIcon(IBinder token, Icon icon, String contentDescription) { 233 CustomTile customTile = getTileForToken(token); 234 if (customTile != null) { 235 verifyCaller(customTile); 236 try { 237 ComponentName componentName = customTile.getComponent(); 238 String packageName = componentName.getPackageName(); 239 UserHandle userHandle = getCallingUserHandle(); 240 PackageInfo info = mContext.getPackageManager().getPackageInfoAsUser(packageName, 0, 241 userHandle.getIdentifier()); 242 if (info.applicationInfo.isSystemApp()) { 243 final StatusBarIcon statusIcon = icon != null 244 ? new StatusBarIcon(userHandle, packageName, icon, 0, 0, 245 contentDescription) 246 : null; 247 mMainHandler.post(new Runnable() { 248 @Override 249 public void run() { 250 StatusBarIconController iconController = mHost.getIconController(); 251 iconController.setIcon(componentName.getClassName(), statusIcon); 252 iconController.setExternalIcon(componentName.getClassName()); 253 } 254 }); 255 } 256 } catch (PackageManager.NameNotFoundException e) { 257 } 258 } 259 } 260 261 @Override 262 public Tile getTile(IBinder token) { 263 CustomTile customTile = getTileForToken(token); 264 if (customTile != null) { 265 verifyCaller(customTile); 266 return customTile.getQsTile(); 267 } 268 return null; 269 } 270 271 @Override 272 public void startUnlockAndRun(IBinder token) { 273 CustomTile customTile = getTileForToken(token); 274 if (customTile != null) { 275 verifyCaller(customTile); 276 customTile.startUnlockAndRun(); 277 } 278 } 279 280 @Override 281 public boolean isLocked() { 282 KeyguardMonitor keyguardMonitor = Dependency.get(KeyguardMonitor.class); 283 return keyguardMonitor.isShowing(); 284 } 285 286 @Override 287 public boolean isSecure() { 288 KeyguardMonitor keyguardMonitor = Dependency.get(KeyguardMonitor.class); 289 return keyguardMonitor.isSecure() && keyguardMonitor.isShowing(); 290 } 291 292 private CustomTile getTileForToken(IBinder token) { 293 synchronized (mServices) { 294 return mTokenMap.get(token); 295 } 296 } 297 298 private CustomTile getTileForComponent(ComponentName component) { 299 synchronized (mServices) { 300 return mTiles.get(component); 301 } 302 } 303 304 public void destroy() { 305 synchronized (mServices) { 306 mServices.values().forEach(service -> service.handleDestroy()); 307 mContext.unregisterReceiver(mRequestListeningReceiver); 308 } 309 } 310 311 private final BroadcastReceiver mRequestListeningReceiver = new BroadcastReceiver() { 312 @Override 313 public void onReceive(Context context, Intent intent) { 314 if (TileService.ACTION_REQUEST_LISTENING.equals(intent.getAction())) { 315 requestListening( 316 (ComponentName) intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME)); 317 } 318 } 319 }; 320 321 private static final Comparator<TileServiceManager> SERVICE_SORT = 322 new Comparator<TileServiceManager>() { 323 @Override 324 public int compare(TileServiceManager left, TileServiceManager right) { 325 return -Integer.compare(left.getBindPriority(), right.getBindPriority()); 326 } 327 }; 328 } 329