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