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