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 
TileServices(QSTileHost host, Looper looper)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 
getContext()74     public Context getContext() {
75         return mContext;
76     }
77 
getHost()78     public QSTileHost getHost() {
79         return mHost;
80     }
81 
getTileWrapper(CustomTile tile)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 
onCreateTileService(ComponentName component, Tile tile)93     protected TileServiceManager onCreateTileService(ComponentName component, Tile tile) {
94         return new TileServiceManager(this, mHandler, component, tile);
95     }
96 
freeService(CustomTile tile, TileServiceManager service)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             // TileServices doesn't know how to add more than 1 icon per slot, so remove all
106             mMainHandler.post(() -> mHost.getIconController()
107                     .removeAllIconsForSlot(slot));
108         }
109     }
110 
setMemoryPressure(boolean memoryPressure)111     public void setMemoryPressure(boolean memoryPressure) {
112         mMaxBound = memoryPressure ? REDUCED_MAX_BOUND : DEFAULT_MAX_BOUND;
113         recalculateBindAllowance();
114     }
115 
recalculateBindAllowance()116     public void recalculateBindAllowance() {
117         final ArrayList<TileServiceManager> services;
118         synchronized (mServices) {
119             services = new ArrayList<>(mServices.values());
120         }
121         final int N = services.size();
122         if (N > mMaxBound) {
123             long currentTime = System.currentTimeMillis();
124             // Precalculate the priority of services for binding.
125             for (int i = 0; i < N; i++) {
126                 services.get(i).calculateBindPriority(currentTime);
127             }
128             // Sort them so we can bind the most important first.
129             Collections.sort(services, SERVICE_SORT);
130         }
131         int i;
132         // Allow mMaxBound items to bind.
133         for (i = 0; i < mMaxBound && i < N; i++) {
134             services.get(i).setBindAllowed(true);
135         }
136         // The rest aren't allowed to bind for now.
137         while (i < N) {
138             services.get(i).setBindAllowed(false);
139             i++;
140         }
141     }
142 
verifyCaller(CustomTile tile)143     private void verifyCaller(CustomTile tile) {
144         try {
145             String packageName = tile.getComponent().getPackageName();
146             int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
147                     Binder.getCallingUserHandle().getIdentifier());
148             if (Binder.getCallingUid() != uid) {
149                 throw new SecurityException("Component outside caller's uid");
150             }
151         } catch (PackageManager.NameNotFoundException e) {
152             throw new SecurityException(e);
153         }
154     }
155 
requestListening(ComponentName component)156     private void requestListening(ComponentName component) {
157         synchronized (mServices) {
158             CustomTile customTile = getTileForComponent(component);
159             if (customTile == null) {
160                 Log.d("TileServices", "Couldn't find tile for " + component);
161                 return;
162             }
163             TileServiceManager service = mServices.get(customTile);
164             if (!service.isActiveTile()) {
165                 return;
166             }
167             service.setBindRequested(true);
168             try {
169                 service.getTileService().onStartListening();
170             } catch (RemoteException e) {
171             }
172         }
173     }
174 
175     @Override
updateQsTile(Tile tile, IBinder token)176     public void updateQsTile(Tile tile, IBinder token) {
177         CustomTile customTile = getTileForToken(token);
178         if (customTile != null) {
179             verifyCaller(customTile);
180             synchronized (mServices) {
181                 final TileServiceManager tileServiceManager = mServices.get(customTile);
182                 tileServiceManager.clearPendingBind();
183                 tileServiceManager.setLastUpdate(System.currentTimeMillis());
184             }
185             customTile.updateState(tile);
186             customTile.refreshState();
187         }
188     }
189 
190     @Override
onStartSuccessful(IBinder token)191     public void onStartSuccessful(IBinder token) {
192         CustomTile customTile = getTileForToken(token);
193         if (customTile != null) {
194             verifyCaller(customTile);
195             synchronized (mServices) {
196                 final TileServiceManager tileServiceManager = mServices.get(customTile);
197                 tileServiceManager.clearPendingBind();
198             }
199             customTile.refreshState();
200         }
201     }
202 
203     @Override
onShowDialog(IBinder token)204     public void onShowDialog(IBinder token) {
205         CustomTile customTile = getTileForToken(token);
206         if (customTile != null) {
207             verifyCaller(customTile);
208             customTile.onDialogShown();
209             mHost.forceCollapsePanels();
210             mServices.get(customTile).setShowingDialog(true);
211         }
212     }
213 
214     @Override
onDialogHidden(IBinder token)215     public void onDialogHidden(IBinder token) {
216         CustomTile customTile = getTileForToken(token);
217         if (customTile != null) {
218             verifyCaller(customTile);
219             mServices.get(customTile).setShowingDialog(false);
220             customTile.onDialogHidden();
221         }
222     }
223 
224     @Override
onStartActivity(IBinder token)225     public void onStartActivity(IBinder token) {
226         CustomTile customTile = getTileForToken(token);
227         if (customTile != null) {
228             verifyCaller(customTile);
229             mHost.forceCollapsePanels();
230         }
231     }
232 
233     @Override
updateStatusIcon(IBinder token, Icon icon, String contentDescription)234     public void updateStatusIcon(IBinder token, Icon icon, String contentDescription) {
235         CustomTile customTile = getTileForToken(token);
236         if (customTile != null) {
237             verifyCaller(customTile);
238             try {
239                 ComponentName componentName = customTile.getComponent();
240                 String packageName = componentName.getPackageName();
241                 UserHandle userHandle = getCallingUserHandle();
242                 PackageInfo info = mContext.getPackageManager().getPackageInfoAsUser(packageName, 0,
243                         userHandle.getIdentifier());
244                 if (info.applicationInfo.isSystemApp()) {
245                     final StatusBarIcon statusIcon = icon != null
246                             ? new StatusBarIcon(userHandle, packageName, icon, 0, 0,
247                                     contentDescription)
248                             : null;
249                     mMainHandler.post(new Runnable() {
250                         @Override
251                         public void run() {
252                             StatusBarIconController iconController = mHost.getIconController();
253                             iconController.setIcon(componentName.getClassName(), statusIcon);
254                             iconController.setExternalIcon(componentName.getClassName());
255                         }
256                     });
257                 }
258             } catch (PackageManager.NameNotFoundException e) {
259             }
260         }
261     }
262 
263     @Override
getTile(IBinder token)264     public Tile getTile(IBinder token) {
265         CustomTile customTile = getTileForToken(token);
266         if (customTile != null) {
267             verifyCaller(customTile);
268             return customTile.getQsTile();
269         }
270         return null;
271     }
272 
273     @Override
startUnlockAndRun(IBinder token)274     public void startUnlockAndRun(IBinder token) {
275         CustomTile customTile = getTileForToken(token);
276         if (customTile != null) {
277             verifyCaller(customTile);
278             customTile.startUnlockAndRun();
279         }
280     }
281 
282     @Override
isLocked()283     public boolean isLocked() {
284         KeyguardMonitor keyguardMonitor = Dependency.get(KeyguardMonitor.class);
285         return keyguardMonitor.isShowing();
286     }
287 
288     @Override
isSecure()289     public boolean isSecure() {
290         KeyguardMonitor keyguardMonitor = Dependency.get(KeyguardMonitor.class);
291         return keyguardMonitor.isSecure() && keyguardMonitor.isShowing();
292     }
293 
getTileForToken(IBinder token)294     private CustomTile getTileForToken(IBinder token) {
295         synchronized (mServices) {
296             return mTokenMap.get(token);
297         }
298     }
299 
getTileForComponent(ComponentName component)300     private CustomTile getTileForComponent(ComponentName component) {
301         synchronized (mServices) {
302             return mTiles.get(component);
303         }
304     }
305 
destroy()306     public void destroy() {
307         synchronized (mServices) {
308             mServices.values().forEach(service -> service.handleDestroy());
309             mContext.unregisterReceiver(mRequestListeningReceiver);
310         }
311     }
312 
313     private final BroadcastReceiver mRequestListeningReceiver = new BroadcastReceiver() {
314         @Override
315         public void onReceive(Context context, Intent intent) {
316             if (TileService.ACTION_REQUEST_LISTENING.equals(intent.getAction())) {
317                 requestListening(
318                         (ComponentName) intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME));
319             }
320         }
321     };
322 
323     private static final Comparator<TileServiceManager> SERVICE_SORT =
324             new Comparator<TileServiceManager>() {
325         @Override
326         public int compare(TileServiceManager left, TileServiceManager right) {
327             return -Integer.compare(left.getBindPriority(), right.getBindPriority());
328         }
329     };
330 }
331