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.app.ActivityManager;
     19 import android.content.BroadcastReceiver;
     20 import android.content.ComponentName;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.IntentFilter;
     24 import android.content.pm.PackageManager;
     25 import android.content.pm.ResolveInfo;
     26 import android.net.Uri;
     27 import android.os.Handler;
     28 import android.os.UserHandle;
     29 import android.service.quicksettings.IQSTileService;
     30 import android.service.quicksettings.Tile;
     31 import android.service.quicksettings.TileService;
     32 import android.support.annotation.VisibleForTesting;
     33 import android.util.Log;
     34 
     35 import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
     36 
     37 import java.util.List;
     38 
     39 import libcore.util.Objects;
     40 
     41 /**
     42  * Manages the priority which lets {@link TileServices} make decisions about which tiles
     43  * to bind.  Also holds on to and manages the {@link TileLifecycleManager}, informing it
     44  * of when it is allowed to bind based on decisions frome the {@link TileServices}.
     45  */
     46 public class TileServiceManager {
     47 
     48     private static final long MIN_BIND_TIME = 5000;
     49     private static final long UNBIND_DELAY = 30000;
     50 
     51     public static final boolean DEBUG = true;
     52 
     53     private static final String TAG = "TileServiceManager";
     54 
     55     @VisibleForTesting
     56     static final String PREFS_FILE = "CustomTileModes";
     57 
     58     private final TileServices mServices;
     59     private final TileLifecycleManager mStateManager;
     60     private final Handler mHandler;
     61     private boolean mBindRequested;
     62     private boolean mBindAllowed;
     63     private boolean mBound;
     64     private int mPriority;
     65     private boolean mJustBound;
     66     private long mLastUpdate;
     67     private boolean mShowingDialog;
     68     // Whether we have a pending bind going out to the service without a response yet.
     69     // This defaults to true to ensure tiles start out unavailable.
     70     private boolean mPendingBind = true;
     71 
     72     TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
     73             Tile tile) {
     74         this(tileServices, handler, new TileLifecycleManager(handler,
     75                 tileServices.getContext(), tileServices, tile, new Intent().setComponent(component),
     76                 new UserHandle(ActivityManager.getCurrentUser())));
     77     }
     78 
     79     @VisibleForTesting
     80     TileServiceManager(TileServices tileServices, Handler handler,
     81             TileLifecycleManager tileLifecycleManager) {
     82         mServices = tileServices;
     83         mHandler = handler;
     84         mStateManager = tileLifecycleManager;
     85 
     86         IntentFilter filter = new IntentFilter();
     87         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
     88         filter.addDataScheme("package");
     89         mServices.getContext().registerReceiverAsUser(mUninstallReceiver,
     90                 new UserHandle(ActivityManager.getCurrentUser()), filter, null, mHandler);
     91     }
     92 
     93     public void setTileChangeListener(TileChangeListener changeListener) {
     94         mStateManager.setTileChangeListener(changeListener);
     95     }
     96 
     97     public boolean isActiveTile() {
     98         return mStateManager.isActiveTile();
     99     }
    100 
    101     public void setShowingDialog(boolean dialog) {
    102         mShowingDialog = dialog;
    103     }
    104 
    105     public IQSTileService getTileService() {
    106         return mStateManager;
    107     }
    108 
    109     public void setBindRequested(boolean bindRequested) {
    110         if (mBindRequested == bindRequested) return;
    111         mBindRequested = bindRequested;
    112         if (mBindAllowed && mBindRequested && !mBound) {
    113             mHandler.removeCallbacks(mUnbind);
    114             bindService();
    115         } else {
    116             mServices.recalculateBindAllowance();
    117         }
    118         if (mBound && !mBindRequested) {
    119             mHandler.postDelayed(mUnbind, UNBIND_DELAY);
    120         }
    121     }
    122 
    123     public void setLastUpdate(long lastUpdate) {
    124         mLastUpdate = lastUpdate;
    125         if (mBound && isActiveTile()) {
    126             mStateManager.onStopListening();
    127             setBindRequested(false);
    128         }
    129         mServices.recalculateBindAllowance();
    130     }
    131 
    132     public void handleDestroy() {
    133         mServices.getContext().unregisterReceiver(mUninstallReceiver);
    134         mStateManager.handleDestroy();
    135     }
    136 
    137     public void setBindAllowed(boolean allowed) {
    138         if (mBindAllowed == allowed) return;
    139         mBindAllowed = allowed;
    140         if (!mBindAllowed && mBound) {
    141             unbindService();
    142         } else if (mBindAllowed && mBindRequested && !mBound) {
    143             bindService();
    144         }
    145     }
    146 
    147     public boolean hasPendingBind() {
    148         return mPendingBind;
    149     }
    150 
    151     public void clearPendingBind() {
    152         mPendingBind = false;
    153     }
    154 
    155     private void bindService() {
    156         if (mBound) {
    157             Log.e(TAG, "Service already bound");
    158             return;
    159         }
    160         mPendingBind = true;
    161         mBound = true;
    162         mJustBound = true;
    163         mHandler.postDelayed(mJustBoundOver, MIN_BIND_TIME);
    164         mStateManager.setBindService(true);
    165     }
    166 
    167     private void unbindService() {
    168         if (!mBound) {
    169             Log.e(TAG, "Service not bound");
    170             return;
    171         }
    172         mBound = false;
    173         mJustBound = false;
    174         mStateManager.setBindService(false);
    175     }
    176 
    177     public void calculateBindPriority(long currentTime) {
    178         if (mStateManager.hasPendingClick()) {
    179             // Pending click is the most important thing, need to put this service at the top of
    180             // the list to be bound.
    181             mPriority = Integer.MAX_VALUE;
    182         } else if (mShowingDialog) {
    183             // Hang on to services that are showing dialogs so they don't die.
    184             mPriority = Integer.MAX_VALUE - 1;
    185         } else if (mJustBound) {
    186             // If we just bound, lets not thrash on binding/unbinding too much, this is second most
    187             // important.
    188             mPriority = Integer.MAX_VALUE - 2;
    189         } else if (!mBindRequested) {
    190             // Don't care about binding right now, put us last.
    191             mPriority = Integer.MIN_VALUE;
    192         } else {
    193             // Order based on whether this was just updated.
    194             long timeSinceUpdate = currentTime - mLastUpdate;
    195             // Fit compare into integer space for simplicity. Make sure to leave MAX_VALUE and
    196             // MAX_VALUE - 1 for the more important states above.
    197             if (timeSinceUpdate > Integer.MAX_VALUE - 3) {
    198                 mPriority = Integer.MAX_VALUE - 3;
    199             } else {
    200                 mPriority = (int) timeSinceUpdate;
    201             }
    202         }
    203     }
    204 
    205     public int getBindPriority() {
    206         return mPriority;
    207     }
    208 
    209     private final Runnable mUnbind = new Runnable() {
    210         @Override
    211         public void run() {
    212             if (mBound && !mBindRequested) {
    213                 unbindService();
    214             }
    215         }
    216     };
    217 
    218     @VisibleForTesting
    219     final Runnable mJustBoundOver = new Runnable() {
    220         @Override
    221         public void run() {
    222             mJustBound = false;
    223             mServices.recalculateBindAllowance();
    224         }
    225     };
    226 
    227     private final BroadcastReceiver mUninstallReceiver = new BroadcastReceiver() {
    228         @Override
    229         public void onReceive(Context context, Intent intent) {
    230             if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
    231                 return;
    232             }
    233 
    234             Uri data = intent.getData();
    235             String pkgName = data.getEncodedSchemeSpecificPart();
    236             final ComponentName component = mStateManager.getComponent();
    237             if (!Objects.equal(pkgName, component.getPackageName())) {
    238                 return;
    239             }
    240 
    241             // If the package is being updated, verify the component still exists.
    242             if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
    243                 Intent queryIntent = new Intent(TileService.ACTION_QS_TILE);
    244                 queryIntent.setPackage(pkgName);
    245                 PackageManager pm = context.getPackageManager();
    246                 List<ResolveInfo> services = pm.queryIntentServicesAsUser(
    247                         queryIntent, 0, ActivityManager.getCurrentUser());
    248                 for (ResolveInfo info : services) {
    249                     if (Objects.equal(info.serviceInfo.packageName, component.getPackageName())
    250                             && Objects.equal(info.serviceInfo.name, component.getClassName())) {
    251                         return;
    252                     }
    253                 }
    254             }
    255 
    256             mServices.getHost().removeTile(component);
    257         }
    258     };
    259 }
    260