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.Looper;
29 import android.os.RemoteException;
30 import android.os.UserHandle;
31 import android.service.quicksettings.IQSService;
32 import android.service.quicksettings.Tile;
33 import android.service.quicksettings.TileService;
34 import android.util.ArrayMap;
35 import android.util.Log;
36 
37 import com.android.internal.statusbar.StatusBarIcon;
38 import com.android.systemui.statusbar.phone.QSTileHost;
39 import com.android.systemui.statusbar.phone.StatusBarIconController;
40 import com.android.systemui.statusbar.policy.KeyguardMonitor;
41 
42 import java.util.ArrayList;
43 import java.util.Collections;
44 import java.util.Comparator;
45 
46 /**
47  * Runs the day-to-day operations of which tiles should be bound and when.
48  */
49 public class TileServices extends IQSService.Stub {
50     static final int DEFAULT_MAX_BOUND = 3;
51     static final int REDUCED_MAX_BOUND = 1;
52 
53     private final ArrayMap<CustomTile, TileServiceManager> mServices = new ArrayMap<>();
54     private final ArrayMap<ComponentName, CustomTile> mTiles = new ArrayMap<>();
55     private final Context mContext;
56     private final Handler mHandler;
57     private final Handler mMainHandler;
58     private final QSTileHost mHost;
59 
60     private int mMaxBound = DEFAULT_MAX_BOUND;
61 
TileServices(QSTileHost host, Looper looper)62     public TileServices(QSTileHost host, Looper looper) {
63         mHost = host;
64         mContext = mHost.getContext();
65         mContext.registerReceiver(mRequestListeningReceiver,
66                 new IntentFilter(TileService.ACTION_REQUEST_LISTENING));
67         mHandler = new Handler(looper);
68         mMainHandler = new Handler(Looper.getMainLooper());
69     }
70 
getContext()71     public Context getContext() {
72         return mContext;
73     }
74 
getHost()75     public QSTileHost getHost() {
76         return mHost;
77     }
78 
getTileWrapper(CustomTile tile)79     public TileServiceManager getTileWrapper(CustomTile tile) {
80         ComponentName component = tile.getComponent();
81         TileServiceManager service = onCreateTileService(component, tile.getQsTile());
82         synchronized (mServices) {
83             mServices.put(tile, service);
84             mTiles.put(component, tile);
85         }
86         return service;
87     }
88 
onCreateTileService(ComponentName component, Tile tile)89     protected TileServiceManager onCreateTileService(ComponentName component, Tile tile) {
90         return new TileServiceManager(this, mHandler, component, tile);
91     }
92 
freeService(CustomTile tile, TileServiceManager service)93     public void freeService(CustomTile tile, TileServiceManager service) {
94         synchronized (mServices) {
95             service.setBindAllowed(false);
96             service.handleDestroy();
97             mServices.remove(tile);
98             mTiles.remove(tile.getComponent());
99             final String slot = tile.getComponent().getClassName();
100             mMainHandler.post(new Runnable() {
101                 @Override
102                 public void run() {
103                     mHost.getIconController().removeIcon(slot);
104                 }
105             });
106         }
107     }
108 
setMemoryPressure(boolean memoryPressure)109     public void setMemoryPressure(boolean memoryPressure) {
110         mMaxBound = memoryPressure ? REDUCED_MAX_BOUND : DEFAULT_MAX_BOUND;
111         recalculateBindAllowance();
112     }
113 
recalculateBindAllowance()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 
verifyCaller(String packageName)141     private void verifyCaller(String packageName) {
142         try {
143             int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
144                     Binder.getCallingUserHandle().getIdentifier());
145             if (Binder.getCallingUid() != uid) {
146                 throw new SecurityException("Component outside caller's uid");
147             }
148         } catch (PackageManager.NameNotFoundException e) {
149             throw new SecurityException(e);
150         }
151     }
152 
requestListening(ComponentName component)153     private void requestListening(ComponentName component) {
154         synchronized (mServices) {
155             CustomTile customTile = getTileForComponent(component);
156             if (customTile == null) {
157                 Log.d("TileServices", "Couldn't find tile for " + component);
158                 return;
159             }
160             TileServiceManager service = mServices.get(customTile);
161             if (!service.isActiveTile()) {
162                 return;
163             }
164             service.setBindRequested(true);
165             try {
166                 service.getTileService().onStartListening();
167             } catch (RemoteException e) {
168             }
169         }
170     }
171 
172     @Override
updateQsTile(Tile tile)173     public void updateQsTile(Tile tile) {
174         ComponentName componentName = tile.getComponentName();
175         verifyCaller(componentName.getPackageName());
176         CustomTile customTile = getTileForComponent(componentName);
177         if (customTile != null) {
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
onStartSuccessful(Tile tile)189     public void onStartSuccessful(Tile tile) {
190         ComponentName componentName = tile.getComponentName();
191         verifyCaller(componentName.getPackageName());
192         CustomTile customTile = getTileForComponent(componentName);
193         if (customTile != null) {
194             synchronized (mServices) {
195                 final TileServiceManager tileServiceManager = mServices.get(customTile);
196                 tileServiceManager.clearPendingBind();
197             }
198             customTile.refreshState();
199         }
200     }
201 
202     @Override
onShowDialog(Tile tile)203     public void onShowDialog(Tile tile) {
204         ComponentName componentName = tile.getComponentName();
205         verifyCaller(componentName.getPackageName());
206         CustomTile customTile = getTileForComponent(componentName);
207         if (customTile != null) {
208             customTile.onDialogShown();
209             mHost.collapsePanels();
210             mServices.get(customTile).setShowingDialog(true);
211         }
212     }
213 
214     @Override
onDialogHidden(Tile tile)215     public void onDialogHidden(Tile tile) {
216         ComponentName componentName = tile.getComponentName();
217         verifyCaller(componentName.getPackageName());
218         CustomTile customTile = getTileForComponent(componentName);
219         if (customTile != null) {
220             mServices.get(customTile).setShowingDialog(false);
221             customTile.onDialogHidden();
222         }
223     }
224 
225     @Override
onStartActivity(Tile tile)226     public void onStartActivity(Tile tile) {
227         ComponentName componentName = tile.getComponentName();
228         verifyCaller(componentName.getPackageName());
229         CustomTile customTile = getTileForComponent(componentName);
230         if (customTile != null) {
231             mHost.collapsePanels();
232         }
233     }
234 
235     @Override
updateStatusIcon(Tile tile, Icon icon, String contentDescription)236     public void updateStatusIcon(Tile tile, Icon icon, String contentDescription) {
237         final ComponentName componentName = tile.getComponentName();
238         String packageName = componentName.getPackageName();
239         verifyCaller(packageName);
240         CustomTile customTile = getTileForComponent(componentName);
241         if (customTile != null) {
242             try {
243                 UserHandle userHandle = getCallingUserHandle();
244                 PackageInfo info = mContext.getPackageManager().getPackageInfoAsUser(packageName, 0,
245                         userHandle.getIdentifier());
246                 if (info.applicationInfo.isSystemApp()) {
247                     final StatusBarIcon statusIcon = icon != null
248                             ? new StatusBarIcon(userHandle, packageName, icon, 0, 0,
249                                     contentDescription)
250                             : null;
251                     mMainHandler.post(new Runnable() {
252                         @Override
253                         public void run() {
254                             StatusBarIconController iconController = mHost.getIconController();
255                             iconController.setIcon(componentName.getClassName(), statusIcon);
256                             iconController.setExternalIcon(componentName.getClassName());
257                         }
258                     });
259                 }
260             } catch (PackageManager.NameNotFoundException e) {
261             }
262         }
263     }
264 
265     @Override
getTile(ComponentName componentName)266     public Tile getTile(ComponentName componentName) {
267         verifyCaller(componentName.getPackageName());
268         CustomTile customTile = getTileForComponent(componentName);
269         if (customTile != null) {
270             return customTile.getQsTile();
271         }
272         return null;
273     }
274 
275     @Override
startUnlockAndRun(Tile tile)276     public void startUnlockAndRun(Tile tile) {
277         ComponentName componentName = tile.getComponentName();
278         verifyCaller(componentName.getPackageName());
279         CustomTile customTile = getTileForComponent(componentName);
280         if (customTile != null) {
281             customTile.startUnlockAndRun();
282         }
283     }
284 
285     @Override
isLocked()286     public boolean isLocked() {
287         KeyguardMonitor keyguardMonitor = mHost.getKeyguardMonitor();
288         return keyguardMonitor.isShowing();
289     }
290 
291     @Override
isSecure()292     public boolean isSecure() {
293         KeyguardMonitor keyguardMonitor = mHost.getKeyguardMonitor();
294         return keyguardMonitor.isSecure() && keyguardMonitor.isShowing();
295     }
296 
getTileForComponent(ComponentName component)297     private CustomTile getTileForComponent(ComponentName component) {
298         synchronized (mServices) {
299             return mTiles.get(component);
300         }
301     }
302 
303     private final BroadcastReceiver mRequestListeningReceiver = new BroadcastReceiver() {
304         @Override
305         public void onReceive(Context context, Intent intent) {
306             if (TileService.ACTION_REQUEST_LISTENING.equals(intent.getAction())) {
307                 requestListening(
308                         (ComponentName) intent.getParcelableExtra(TileService.EXTRA_COMPONENT));
309             }
310         }
311     };
312 
313     private static final Comparator<TileServiceManager> SERVICE_SORT =
314             new Comparator<TileServiceManager>() {
315         @Override
316         public int compare(TileServiceManager left, TileServiceManager right) {
317             return -Integer.compare(left.getBindPriority(), right.getBindPriority());
318         }
319     };
320 }
321