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