• 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.app.PendingIntent;
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.pm.PackageInfo;
22 import android.content.pm.PackageManager;
23 import android.graphics.drawable.Icon;
24 import android.os.Binder;
25 import android.os.Handler;
26 import android.os.IBinder;
27 import android.os.RemoteException;
28 import android.os.UserHandle;
29 import android.service.quicksettings.IQSService;
30 import android.service.quicksettings.Tile;
31 import android.util.ArrayMap;
32 import android.util.Log;
33 
34 import androidx.annotation.NonNull;
35 import androidx.annotation.Nullable;
36 import androidx.annotation.VisibleForTesting;
37 
38 import com.android.internal.statusbar.StatusBarIcon;
39 import com.android.systemui.broadcast.BroadcastDispatcher;
40 import com.android.systemui.dagger.SysUISingleton;
41 import com.android.systemui.dagger.qualifiers.Main;
42 import com.android.systemui.qs.QSHost;
43 import com.android.systemui.settings.UserTracker;
44 import com.android.systemui.statusbar.CommandQueue;
45 import com.android.systemui.statusbar.phone.StatusBarIconController;
46 import com.android.systemui.statusbar.policy.KeyguardStateController;
47 
48 import java.util.ArrayList;
49 import java.util.Collections;
50 import java.util.Comparator;
51 import java.util.Objects;
52 
53 import javax.inject.Inject;
54 import javax.inject.Provider;
55 
56 /**
57  * Runs the day-to-day operations of which tiles should be bound and when.
58  */
59 @SysUISingleton
60 public class TileServices extends IQSService.Stub {
61     static final int DEFAULT_MAX_BOUND = 3;
62     static final int REDUCED_MAX_BOUND = 1;
63     private static final String TAG = "TileServices";
64 
65     private final ArrayMap<CustomTile, TileServiceManager> mServices = new ArrayMap<>();
66     private final ArrayMap<ComponentName, CustomTile> mTiles = new ArrayMap<>();
67     private final ArrayMap<IBinder, CustomTile> mTokenMap = new ArrayMap<>();
68     private final Context mContext;
69     private final Handler mMainHandler;
70     private final Provider<Handler> mHandlerProvider;
71     private final QSHost mHost;
72     private final KeyguardStateController mKeyguardStateController;
73     private final BroadcastDispatcher mBroadcastDispatcher;
74     private final CommandQueue mCommandQueue;
75     private final UserTracker mUserTracker;
76     private final StatusBarIconController mStatusBarIconController;
77 
78     private int mMaxBound = DEFAULT_MAX_BOUND;
79 
80     @Inject
TileServices( QSHost host, @Main Provider<Handler> handlerProvider, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker, KeyguardStateController keyguardStateController, CommandQueue commandQueue, StatusBarIconController statusBarIconController)81     public TileServices(
82             QSHost host,
83             @Main Provider<Handler> handlerProvider,
84             BroadcastDispatcher broadcastDispatcher,
85             UserTracker userTracker,
86             KeyguardStateController keyguardStateController,
87             CommandQueue commandQueue,
88             StatusBarIconController statusBarIconController) {
89         mHost = host;
90         mKeyguardStateController = keyguardStateController;
91         mContext = mHost.getContext();
92         mBroadcastDispatcher = broadcastDispatcher;
93         mHandlerProvider = handlerProvider;
94         mMainHandler = mHandlerProvider.get();
95         mUserTracker = userTracker;
96         mCommandQueue = commandQueue;
97         mStatusBarIconController = statusBarIconController;
98         mCommandQueue.addCallback(mRequestListeningCallback);
99     }
100 
getContext()101     public Context getContext() {
102         return mContext;
103     }
104 
getHost()105     public QSHost getHost() {
106         return mHost;
107     }
108 
getTileWrapper(CustomTile tile)109     public TileServiceManager getTileWrapper(CustomTile tile) {
110         ComponentName component = tile.getComponent();
111         TileServiceManager service = onCreateTileService(component, mBroadcastDispatcher);
112         synchronized (mServices) {
113             mServices.put(tile, service);
114             mTiles.put(component, tile);
115             mTokenMap.put(service.getToken(), tile);
116         }
117         // Makes sure binding only happens after the maps have been populated
118         service.startLifecycleManagerAndAddTile();
119         return service;
120     }
121 
onCreateTileService(ComponentName component, BroadcastDispatcher broadcastDispatcher)122     protected TileServiceManager onCreateTileService(ComponentName component,
123             BroadcastDispatcher broadcastDispatcher) {
124         return new TileServiceManager(this, mHandlerProvider.get(), component,
125                 broadcastDispatcher, mUserTracker);
126     }
127 
freeService(CustomTile tile, TileServiceManager service)128     public void freeService(CustomTile tile, TileServiceManager service) {
129         synchronized (mServices) {
130             service.setBindAllowed(false);
131             service.handleDestroy();
132             mServices.remove(tile);
133             mTokenMap.remove(service.getToken());
134             mTiles.remove(tile.getComponent());
135             final String slot = getStatusBarIconSlotName(tile.getComponent());
136             mMainHandler.post(() -> mStatusBarIconController.removeIconForTile(slot));
137         }
138     }
139 
setMemoryPressure(boolean memoryPressure)140     public void setMemoryPressure(boolean memoryPressure) {
141         mMaxBound = memoryPressure ? REDUCED_MAX_BOUND : DEFAULT_MAX_BOUND;
142         recalculateBindAllowance();
143     }
144 
recalculateBindAllowance()145     public void recalculateBindAllowance() {
146         final ArrayList<TileServiceManager> services;
147         synchronized (mServices) {
148             services = new ArrayList<>(mServices.values());
149         }
150         final int N = services.size();
151         if (N > mMaxBound) {
152             long currentTime = System.currentTimeMillis();
153             // Precalculate the priority of services for binding.
154             for (int i = 0; i < N; i++) {
155                 services.get(i).calculateBindPriority(currentTime);
156             }
157             // Sort them so we can bind the most important first.
158             Collections.sort(services, SERVICE_SORT);
159         }
160         int i;
161         // Allow mMaxBound items to bind.
162         for (i = 0; i < mMaxBound && i < N; i++) {
163             services.get(i).setBindAllowed(true);
164         }
165         // The rest aren't allowed to bind for now.
166         while (i < N) {
167             services.get(i).setBindAllowed(false);
168             i++;
169         }
170     }
171 
verifyCaller(CustomTile tile)172     private void verifyCaller(CustomTile tile) {
173         try {
174             String packageName = tile.getComponent().getPackageName();
175             int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
176                     Binder.getCallingUserHandle().getIdentifier());
177             if (Binder.getCallingUid() != uid) {
178                 throw new SecurityException("Component outside caller's uid");
179             }
180         } catch (PackageManager.NameNotFoundException e) {
181             throw new SecurityException(e);
182         }
183     }
184 
requestListening(ComponentName component)185     private void requestListening(ComponentName component) {
186         synchronized (mServices) {
187             CustomTile customTile = getTileForComponent(component);
188             if (customTile == null) {
189                 Log.d("TileServices", "Couldn't find tile for " + component);
190                 return;
191             }
192             TileServiceManager service = mServices.get(customTile);
193             if (service == null) {
194                 Log.e(
195                         TAG,
196                         "No TileServiceManager found in requestListening for tile "
197                                 + customTile.getTileSpec());
198                 return;
199             }
200             if (!service.isActiveTile()) {
201                 return;
202             }
203             service.setBindRequested(true);
204             try {
205                 service.getTileService().onStartListening();
206             } catch (RemoteException e) {
207             }
208         }
209     }
210 
211     @Override
updateQsTile(Tile tile, IBinder token)212     public void updateQsTile(Tile tile, IBinder token) {
213         CustomTile customTile = getTileForToken(token);
214         if (customTile != null) {
215             verifyCaller(customTile);
216             synchronized (mServices) {
217                 final TileServiceManager tileServiceManager = mServices.get(customTile);
218                 if (tileServiceManager == null || !tileServiceManager.isLifecycleStarted()) {
219                     Log.e(TAG, "TileServiceManager not started for " + customTile.getComponent(),
220                             new IllegalStateException());
221                     return;
222                 }
223                 tileServiceManager.clearPendingBind();
224                 tileServiceManager.setLastUpdate(System.currentTimeMillis());
225             }
226             customTile.updateTileState(tile);
227             customTile.refreshState();
228         }
229     }
230 
231     @Override
onStartSuccessful(IBinder token)232     public void onStartSuccessful(IBinder token) {
233         CustomTile customTile = getTileForToken(token);
234         if (customTile != null) {
235             verifyCaller(customTile);
236             synchronized (mServices) {
237                 final TileServiceManager tileServiceManager = mServices.get(customTile);
238                 // This should not happen as the TileServiceManager should have been started for the
239                 // first bind to happen.
240                 if (tileServiceManager == null || !tileServiceManager.isLifecycleStarted()) {
241                     Log.e(TAG, "TileServiceManager not started for " + customTile.getComponent(),
242                             new IllegalStateException());
243                     return;
244                 }
245                 tileServiceManager.clearPendingBind();
246             }
247             customTile.refreshState();
248         }
249     }
250 
251     @Override
onShowDialog(IBinder token)252     public void onShowDialog(IBinder token) {
253         CustomTile customTile = getTileForToken(token);
254         if (customTile != null) {
255             verifyCaller(customTile);
256             customTile.onDialogShown();
257             mHost.forceCollapsePanels();
258             Objects.requireNonNull(mServices.get(customTile)).setShowingDialog(true);
259         }
260     }
261 
262     @Override
onDialogHidden(IBinder token)263     public void onDialogHidden(IBinder token) {
264         CustomTile customTile = getTileForToken(token);
265         if (customTile != null) {
266             verifyCaller(customTile);
267             Objects.requireNonNull(mServices.get(customTile)).setShowingDialog(false);
268             customTile.onDialogHidden();
269         }
270     }
271 
272     @Override
onStartActivity(IBinder token)273     public void onStartActivity(IBinder token) {
274         CustomTile customTile = getTileForToken(token);
275         if (customTile != null) {
276             verifyCaller(customTile);
277             mHost.forceCollapsePanels();
278         }
279     }
280 
281     @Override
startActivity(IBinder token, PendingIntent pendingIntent)282     public void startActivity(IBinder token, PendingIntent pendingIntent) {
283         startActivity(getTileForToken(token), pendingIntent);
284     }
285 
286     @VisibleForTesting
startActivity(CustomTile customTile, PendingIntent pendingIntent)287     protected void startActivity(CustomTile customTile, PendingIntent pendingIntent) {
288         if (customTile != null) {
289             verifyCaller(customTile);
290             customTile.startActivityAndCollapse(pendingIntent);
291         }
292     }
293 
294     @Override
updateStatusIcon(IBinder token, Icon icon, String contentDescription)295     public void updateStatusIcon(IBinder token, Icon icon, String contentDescription) {
296         CustomTile customTile = getTileForToken(token);
297         if (customTile != null) {
298             verifyCaller(customTile);
299             try {
300                 ComponentName componentName = customTile.getComponent();
301                 String packageName = componentName.getPackageName();
302                 UserHandle userHandle = getCallingUserHandle();
303                 PackageInfo info = mContext.getPackageManager().getPackageInfoAsUser(packageName, 0,
304                         userHandle.getIdentifier());
305                 if (info.applicationInfo.isSystemApp()) {
306                     final StatusBarIcon statusIcon = icon != null
307                             ? new StatusBarIcon(userHandle, packageName, icon, 0, 0,
308                                     contentDescription)
309                             : null;
310                     final String slot = getStatusBarIconSlotName(componentName);
311                     mMainHandler.post(new Runnable() {
312                         @Override
313                         public void run() {
314                             mStatusBarIconController.setIconFromTile(slot, statusIcon);
315                         }
316                     });
317                 }
318             } catch (PackageManager.NameNotFoundException e) {
319             }
320         }
321     }
322 
323     @Nullable
324     @Override
getTile(IBinder token)325     public Tile getTile(IBinder token) {
326         CustomTile customTile = getTileForToken(token);
327         if (customTile != null) {
328             verifyCaller(customTile);
329             return customTile.getQsTile();
330         }
331         return null;
332     }
333 
334     @Override
startUnlockAndRun(IBinder token)335     public void startUnlockAndRun(IBinder token) {
336         CustomTile customTile = getTileForToken(token);
337         if (customTile != null) {
338             verifyCaller(customTile);
339             customTile.startUnlockAndRun();
340         }
341     }
342 
343     @Override
isLocked()344     public boolean isLocked() {
345         return mKeyguardStateController.isShowing();
346     }
347 
348     @Override
isSecure()349     public boolean isSecure() {
350         return mKeyguardStateController.isMethodSecure() && mKeyguardStateController.isShowing();
351     }
352 
353     @Nullable
getTileForToken(IBinder token)354     public CustomTile getTileForToken(IBinder token) {
355         synchronized (mServices) {
356             return mTokenMap.get(token);
357         }
358     }
359 
360     @Nullable
getTileForComponent(ComponentName component)361     private CustomTile getTileForComponent(ComponentName component) {
362         synchronized (mServices) {
363             return mTiles.get(component);
364         }
365     }
366 
destroy()367     public void destroy() {
368         synchronized (mServices) {
369             mServices.values().forEach(service -> service.handleDestroy());
370         }
371         mCommandQueue.removeCallback(mRequestListeningCallback);
372     }
373 
374     /** Returns the slot name that should be used when adding or removing status bar icons. */
getStatusBarIconSlotName(ComponentName componentName)375     private String getStatusBarIconSlotName(ComponentName componentName) {
376         return componentName.getClassName();
377     }
378 
379 
380     private final CommandQueue.Callbacks mRequestListeningCallback = new CommandQueue.Callbacks() {
381         @Override
382         public void requestTileServiceListeningState(@NonNull ComponentName componentName) {
383             mMainHandler.post(() -> requestListening(componentName));
384         }
385     };
386 
387     private static final Comparator<TileServiceManager> SERVICE_SORT =
388             new Comparator<TileServiceManager>() {
389         @Override
390         public int compare(TileServiceManager left, TileServiceManager right) {
391             return -Integer.compare(left.getBindPriority(), right.getBindPriority());
392         }
393     };
394 }
395