TileServices.java revision a3453b8bd9af44566c6a31fd0156cc76e6028f6d
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 */
16package com.android.systemui.qs.external;
17
18import android.content.BroadcastReceiver;
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.content.pm.PackageInfo;
24import android.content.pm.PackageManager;
25import android.graphics.drawable.Icon;
26import android.os.Binder;
27import android.os.Handler;
28import android.os.Looper;
29import android.os.RemoteException;
30import android.os.UserHandle;
31import android.service.quicksettings.IQSService;
32import android.service.quicksettings.Tile;
33import android.service.quicksettings.TileService;
34import android.util.ArrayMap;
35import android.util.Log;
36
37import com.android.internal.statusbar.StatusBarIcon;
38import com.android.systemui.statusbar.phone.QSTileHost;
39import com.android.systemui.statusbar.phone.StatusBarIconController;
40import com.android.systemui.statusbar.policy.KeyguardMonitor;
41
42import java.util.ArrayList;
43import java.util.Collections;
44import java.util.Comparator;
45
46/**
47 * Runs the day-to-day operations of which tiles should be bound and when.
48 */
49public 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
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
71    public Context getContext() {
72        return mContext;
73    }
74
75    public QSTileHost getHost() {
76        return mHost;
77    }
78
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
89    protected TileServiceManager onCreateTileService(ComponentName component, Tile tile) {
90        return new TileServiceManager(this, mHandler, component, tile);
91    }
92
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
109    public void setMemoryPressure(boolean memoryPressure) {
110        mMaxBound = memoryPressure ? REDUCED_MAX_BOUND : DEFAULT_MAX_BOUND;
111        recalculateBindAllowance();
112    }
113
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
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
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
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
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
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
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
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
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
266    public void startUnlockAndRun(Tile tile) {
267        ComponentName componentName = tile.getComponentName();
268        verifyCaller(componentName.getPackageName());
269        CustomTile customTile = getTileForComponent(componentName);
270        if (customTile != null) {
271            customTile.startUnlockAndRun();
272        }
273    }
274
275    @Override
276    public boolean isLocked() {
277        KeyguardMonitor keyguardMonitor = mHost.getKeyguardMonitor();
278        return keyguardMonitor.isShowing();
279    }
280
281    @Override
282    public boolean isSecure() {
283        KeyguardMonitor keyguardMonitor = mHost.getKeyguardMonitor();
284        return keyguardMonitor.isSecure() && keyguardMonitor.isShowing();
285    }
286
287    private CustomTile getTileForComponent(ComponentName component) {
288        synchronized (mServices) {
289            return mTiles.get(component);
290        }
291    }
292
293    private final BroadcastReceiver mRequestListeningReceiver = new BroadcastReceiver() {
294        @Override
295        public void onReceive(Context context, Intent intent) {
296            if (TileService.ACTION_REQUEST_LISTENING.equals(intent.getAction())) {
297                requestListening(
298                        (ComponentName) intent.getParcelableExtra(TileService.EXTRA_COMPONENT));
299            }
300        }
301    };
302
303    private static final Comparator<TileServiceManager> SERVICE_SORT =
304            new Comparator<TileServiceManager>() {
305        @Override
306        public int compare(TileServiceManager left, TileServiceManager right) {
307            return -Integer.compare(left.getBindPriority(), right.getBindPriority());
308        }
309    };
310}
311