Home | History | Annotate | Download | only in external
      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 
     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 
     74     public Context getContext() {
     75         return mContext;
     76     }
     77 
     78     public QSTileHost getHost() {
     79         return mHost;
     80     }
     81 
     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 
     93     protected TileServiceManager onCreateTileService(ComponentName component, Tile tile) {
     94         return new TileServiceManager(this, mHandler, component, tile);
     95     }
     96 
     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             mMainHandler.post(() -> mHost.getIconController().removeIcon(slot));
    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(CustomTile tile) {
    142         try {
    143             String packageName = tile.getComponent().getPackageName();
    144             int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
    145                     Binder.getCallingUserHandle().getIdentifier());
    146             if (Binder.getCallingUid() != uid) {
    147                 throw new SecurityException("Component outside caller's uid");
    148             }
    149         } catch (PackageManager.NameNotFoundException e) {
    150             throw new SecurityException(e);
    151         }
    152     }
    153 
    154     private void requestListening(ComponentName component) {
    155         synchronized (mServices) {
    156             CustomTile customTile = getTileForComponent(component);
    157             if (customTile == null) {
    158                 Log.d("TileServices", "Couldn't find tile for " + component);
    159                 return;
    160             }
    161             TileServiceManager service = mServices.get(customTile);
    162             if (!service.isActiveTile()) {
    163                 return;
    164             }
    165             service.setBindRequested(true);
    166             try {
    167                 service.getTileService().onStartListening();
    168             } catch (RemoteException e) {
    169             }
    170         }
    171     }
    172 
    173     @Override
    174     public void updateQsTile(Tile tile, IBinder token) {
    175         CustomTile customTile = getTileForToken(token);
    176         if (customTile != null) {
    177             verifyCaller(customTile);
    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(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                 tileServiceManager.clearPendingBind();
    196             }
    197             customTile.refreshState();
    198         }
    199     }
    200 
    201     @Override
    202     public void onShowDialog(IBinder token) {
    203         CustomTile customTile = getTileForToken(token);
    204         if (customTile != null) {
    205             verifyCaller(customTile);
    206             customTile.onDialogShown();
    207             mHost.forceCollapsePanels();
    208             mServices.get(customTile).setShowingDialog(true);
    209         }
    210     }
    211 
    212     @Override
    213     public void onDialogHidden(IBinder token) {
    214         CustomTile customTile = getTileForToken(token);
    215         if (customTile != null) {
    216             verifyCaller(customTile);
    217             mServices.get(customTile).setShowingDialog(false);
    218             customTile.onDialogHidden();
    219         }
    220     }
    221 
    222     @Override
    223     public void onStartActivity(IBinder token) {
    224         CustomTile customTile = getTileForToken(token);
    225         if (customTile != null) {
    226             verifyCaller(customTile);
    227             mHost.forceCollapsePanels();
    228         }
    229     }
    230 
    231     @Override
    232     public void updateStatusIcon(IBinder token, Icon icon, String contentDescription) {
    233         CustomTile customTile = getTileForToken(token);
    234         if (customTile != null) {
    235             verifyCaller(customTile);
    236             try {
    237                 ComponentName componentName = customTile.getComponent();
    238                 String packageName = componentName.getPackageName();
    239                 UserHandle userHandle = getCallingUserHandle();
    240                 PackageInfo info = mContext.getPackageManager().getPackageInfoAsUser(packageName, 0,
    241                         userHandle.getIdentifier());
    242                 if (info.applicationInfo.isSystemApp()) {
    243                     final StatusBarIcon statusIcon = icon != null
    244                             ? new StatusBarIcon(userHandle, packageName, icon, 0, 0,
    245                                     contentDescription)
    246                             : null;
    247                     mMainHandler.post(new Runnable() {
    248                         @Override
    249                         public void run() {
    250                             StatusBarIconController iconController = mHost.getIconController();
    251                             iconController.setIcon(componentName.getClassName(), statusIcon);
    252                             iconController.setExternalIcon(componentName.getClassName());
    253                         }
    254                     });
    255                 }
    256             } catch (PackageManager.NameNotFoundException e) {
    257             }
    258         }
    259     }
    260 
    261     @Override
    262     public Tile getTile(IBinder token) {
    263         CustomTile customTile = getTileForToken(token);
    264         if (customTile != null) {
    265             verifyCaller(customTile);
    266             return customTile.getQsTile();
    267         }
    268         return null;
    269     }
    270 
    271     @Override
    272     public void startUnlockAndRun(IBinder token) {
    273         CustomTile customTile = getTileForToken(token);
    274         if (customTile != null) {
    275             verifyCaller(customTile);
    276             customTile.startUnlockAndRun();
    277         }
    278     }
    279 
    280     @Override
    281     public boolean isLocked() {
    282         KeyguardMonitor keyguardMonitor = Dependency.get(KeyguardMonitor.class);
    283         return keyguardMonitor.isShowing();
    284     }
    285 
    286     @Override
    287     public boolean isSecure() {
    288         KeyguardMonitor keyguardMonitor = Dependency.get(KeyguardMonitor.class);
    289         return keyguardMonitor.isSecure() && keyguardMonitor.isShowing();
    290     }
    291 
    292     private CustomTile getTileForToken(IBinder token) {
    293         synchronized (mServices) {
    294             return mTokenMap.get(token);
    295         }
    296     }
    297 
    298     private CustomTile getTileForComponent(ComponentName component) {
    299         synchronized (mServices) {
    300             return mTiles.get(component);
    301         }
    302     }
    303 
    304     public void destroy() {
    305         synchronized (mServices) {
    306             mServices.values().forEach(service -> service.handleDestroy());
    307             mContext.unregisterReceiver(mRequestListeningReceiver);
    308         }
    309     }
    310 
    311     private final BroadcastReceiver mRequestListeningReceiver = new BroadcastReceiver() {
    312         @Override
    313         public void onReceive(Context context, Intent intent) {
    314             if (TileService.ACTION_REQUEST_LISTENING.equals(intent.getAction())) {
    315                 requestListening(
    316                         (ComponentName) intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME));
    317             }
    318         }
    319     };
    320 
    321     private static final Comparator<TileServiceManager> SERVICE_SORT =
    322             new Comparator<TileServiceManager>() {
    323         @Override
    324         public int compare(TileServiceManager left, TileServiceManager right) {
    325             return -Integer.compare(left.getBindPriority(), right.getBindPriority());
    326         }
    327     };
    328 }
    329