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