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.AppGlobals;
     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.ServiceConnection;
     25 import android.content.pm.PackageManager;
     26 import android.content.pm.PackageManager.NameNotFoundException;
     27 import android.content.pm.ServiceInfo;
     28 import android.net.Uri;
     29 import android.os.Binder;
     30 import android.os.Handler;
     31 import android.os.IBinder;
     32 import android.os.RemoteException;
     33 import android.os.UserHandle;
     34 import android.service.quicksettings.IQSService;
     35 import android.service.quicksettings.IQSTileService;
     36 import android.service.quicksettings.Tile;
     37 import android.service.quicksettings.TileService;
     38 import android.support.annotation.VisibleForTesting;
     39 import android.util.ArraySet;
     40 import android.util.Log;
     41 
     42 import libcore.util.Objects;
     43 
     44 import java.util.Set;
     45 
     46 /**
     47  * Manages the lifecycle of a TileService.
     48  * <p>
     49  * Will keep track of all calls on the IQSTileService interface and will relay those calls to the
     50  * TileService as soon as it is bound.  It will only bind to the service when it is allowed to
     51  * ({@link #setBindService(boolean)}) and when the service is available.
     52  */
     53 public class TileLifecycleManager extends BroadcastReceiver implements
     54         IQSTileService, ServiceConnection, IBinder.DeathRecipient {
     55     public static final boolean DEBUG = false;
     56 
     57     private static final String TAG = "TileLifecycleManager";
     58 
     59     private static final int MSG_ON_ADDED = 0;
     60     private static final int MSG_ON_REMOVED = 1;
     61     private static final int MSG_ON_CLICK = 2;
     62     private static final int MSG_ON_UNLOCK_COMPLETE = 3;
     63 
     64     // Bind retry control.
     65     private static final int MAX_BIND_RETRIES = 5;
     66     private static final int BIND_RETRY_DELAY = 1000;
     67 
     68     // Shared prefs that hold tile lifecycle info.
     69     private static final String TILES = "tiles_prefs";
     70 
     71     private final Context mContext;
     72     private final Handler mHandler;
     73     private final Intent mIntent;
     74     private final UserHandle mUser;
     75     private final IBinder mToken = new Binder();
     76 
     77     private Set<Integer> mQueuedMessages = new ArraySet<>();
     78     private QSTileServiceWrapper mWrapper;
     79     private boolean mListening;
     80     private IBinder mClickBinder;
     81 
     82     private int mBindTryCount;
     83     private boolean mBound;
     84     @VisibleForTesting
     85     boolean mReceiverRegistered;
     86     private boolean mUnbindImmediate;
     87     private TileChangeListener mChangeListener;
     88     // Return value from bindServiceAsUser, determines whether safe to call unbind.
     89     private boolean mIsBound;
     90 
     91     public TileLifecycleManager(Handler handler, Context context, IQSService service,
     92             Tile tile, Intent intent, UserHandle user) {
     93         mContext = context;
     94         mHandler = handler;
     95         mIntent = intent;
     96         mIntent.putExtra(TileService.EXTRA_SERVICE, service.asBinder());
     97         mIntent.putExtra(TileService.EXTRA_TOKEN, mToken);
     98         mUser = user;
     99         if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser);
    100     }
    101 
    102     public ComponentName getComponent() {
    103         return mIntent.getComponent();
    104     }
    105 
    106     public boolean hasPendingClick() {
    107         synchronized (mQueuedMessages) {
    108             return mQueuedMessages.contains(MSG_ON_CLICK);
    109         }
    110     }
    111 
    112     public boolean isActiveTile() {
    113         try {
    114             ServiceInfo info = mContext.getPackageManager().getServiceInfo(mIntent.getComponent(),
    115                     PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
    116             return info.metaData != null
    117                     && info.metaData.getBoolean(TileService.META_DATA_ACTIVE_TILE, false);
    118         } catch (NameNotFoundException e) {
    119             return false;
    120         }
    121     }
    122 
    123     /**
    124      * Binds just long enough to send any queued messages, then unbinds.
    125      */
    126     public void flushMessagesAndUnbind() {
    127         mUnbindImmediate = true;
    128         setBindService(true);
    129     }
    130 
    131     public void setBindService(boolean bind) {
    132         if (mBound && mUnbindImmediate) {
    133             // If we are already bound and expecting to unbind, this means we should stay bound
    134             // because something else wants to hold the connection open.
    135             mUnbindImmediate = false;
    136             return;
    137         }
    138         mBound = bind;
    139         if (bind) {
    140             if (mBindTryCount == MAX_BIND_RETRIES) {
    141                 // Too many failures, give up on this tile until an update.
    142                 startPackageListening();
    143                 return;
    144             }
    145             if (!checkComponentState()) {
    146                 return;
    147             }
    148             if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser);
    149             mBindTryCount++;
    150             try {
    151                 mIsBound = mContext.bindServiceAsUser(mIntent, this,
    152                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
    153                         mUser);
    154             } catch (SecurityException e) {
    155                 Log.e(TAG, "Failed to bind to service", e);
    156                 mIsBound = false;
    157             }
    158         } else {
    159             if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser);
    160             // Give it another chance next time it needs to be bound, out of kindness.
    161             mBindTryCount = 0;
    162             mWrapper = null;
    163             if (mIsBound) {
    164                 mContext.unbindService(this);
    165                 mIsBound = false;
    166             }
    167         }
    168     }
    169 
    170     @Override
    171     public void onServiceConnected(ComponentName name, IBinder service) {
    172         if (DEBUG) Log.d(TAG, "onServiceConnected " + name);
    173         // Got a connection, set the binding count to 0.
    174         mBindTryCount = 0;
    175         final QSTileServiceWrapper wrapper = new QSTileServiceWrapper(Stub.asInterface(service));
    176         try {
    177             service.linkToDeath(this, 0);
    178         } catch (RemoteException e) {
    179         }
    180         mWrapper = wrapper;
    181         handlePendingMessages();
    182     }
    183 
    184     @Override
    185     public void onServiceDisconnected(ComponentName name) {
    186         if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name);
    187         handleDeath();
    188     }
    189 
    190     private void handlePendingMessages() {
    191         // This ordering is laid out manually to make sure we preserve the TileService
    192         // lifecycle.
    193         ArraySet<Integer> queue;
    194         synchronized (mQueuedMessages) {
    195             queue = new ArraySet<>(mQueuedMessages);
    196             mQueuedMessages.clear();
    197         }
    198         if (queue.contains(MSG_ON_ADDED)) {
    199             if (DEBUG) Log.d(TAG, "Handling pending onAdded");
    200             onTileAdded();
    201         }
    202         if (mListening) {
    203             if (DEBUG) Log.d(TAG, "Handling pending onStartListening");
    204             onStartListening();
    205         }
    206         if (queue.contains(MSG_ON_CLICK)) {
    207             if (DEBUG) Log.d(TAG, "Handling pending onClick");
    208             if (!mListening) {
    209                 Log.w(TAG, "Managed to get click on non-listening state...");
    210                 // Skipping click since lost click privileges.
    211             } else {
    212                 onClick(mClickBinder);
    213             }
    214         }
    215         if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) {
    216             if (DEBUG) Log.d(TAG, "Handling pending onUnlockComplete");
    217             if (!mListening) {
    218                 Log.w(TAG, "Managed to get unlock on non-listening state...");
    219                 // Skipping unlock since lost click privileges.
    220             } else {
    221                 onUnlockComplete();
    222             }
    223         }
    224         if (queue.contains(MSG_ON_REMOVED)) {
    225             if (DEBUG) Log.d(TAG, "Handling pending onRemoved");
    226             if (mListening) {
    227                 Log.w(TAG, "Managed to get remove in listening state...");
    228                 onStopListening();
    229             }
    230             onTileRemoved();
    231         }
    232         if (mUnbindImmediate) {
    233             mUnbindImmediate = false;
    234             setBindService(false);
    235         }
    236     }
    237 
    238     public void handleDestroy() {
    239         if (DEBUG) Log.d(TAG, "handleDestroy");
    240         if (mReceiverRegistered) {
    241             stopPackageListening();
    242         }
    243     }
    244 
    245     private void handleDeath() {
    246         if (mWrapper == null) return;
    247         mWrapper = null;
    248         if (!mBound) return;
    249         if (DEBUG) Log.d(TAG, "handleDeath");
    250         if (checkComponentState()) {
    251             mHandler.postDelayed(new Runnable() {
    252                 @Override
    253                 public void run() {
    254                     if (mBound) {
    255                         // Retry binding.
    256                         setBindService(true);
    257                     }
    258                 }
    259             }, BIND_RETRY_DELAY);
    260         }
    261     }
    262 
    263     private boolean checkComponentState() {
    264         PackageManager pm = mContext.getPackageManager();
    265         if (!isPackageAvailable(pm) || !isComponentAvailable(pm)) {
    266             startPackageListening();
    267             return false;
    268         }
    269         return true;
    270     }
    271 
    272     private void startPackageListening() {
    273         if (DEBUG) Log.d(TAG, "startPackageListening");
    274         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
    275         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
    276         filter.addDataScheme("package");
    277         mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler);
    278         filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
    279         mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler);
    280         mReceiverRegistered = true;
    281     }
    282 
    283     private void stopPackageListening() {
    284         if (DEBUG) Log.d(TAG, "stopPackageListening");
    285         mContext.unregisterReceiver(this);
    286         mReceiverRegistered = false;
    287     }
    288 
    289     public void setTileChangeListener(TileChangeListener changeListener) {
    290         mChangeListener = changeListener;
    291     }
    292 
    293     @Override
    294     public void onReceive(Context context, Intent intent) {
    295         if (DEBUG) Log.d(TAG, "onReceive: " + intent);
    296         if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
    297             Uri data = intent.getData();
    298             String pkgName = data.getEncodedSchemeSpecificPart();
    299             if (!Objects.equal(pkgName, mIntent.getComponent().getPackageName())) {
    300                 return;
    301             }
    302         }
    303         if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) && mChangeListener != null) {
    304             mChangeListener.onTileChanged(mIntent.getComponent());
    305         }
    306         stopPackageListening();
    307         if (mBound) {
    308             // Trying to bind again will check the state of the package before bothering to bind.
    309             if (DEBUG) Log.d(TAG, "Trying to rebind");
    310             setBindService(true);
    311         }
    312     }
    313 
    314     private boolean isComponentAvailable(PackageManager pm) {
    315         String packageName = mIntent.getComponent().getPackageName();
    316         try {
    317             ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(mIntent.getComponent(),
    318                     0, mUser.getIdentifier());
    319             if (DEBUG && si == null) Log.d(TAG, "Can't find component " + mIntent.getComponent());
    320             return si != null;
    321         } catch (RemoteException e) {
    322             // Shouldn't happen.
    323         }
    324         return false;
    325     }
    326 
    327     private boolean isPackageAvailable(PackageManager pm) {
    328         String packageName = mIntent.getComponent().getPackageName();
    329         try {
    330             pm.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier());
    331             return true;
    332         } catch (PackageManager.NameNotFoundException e) {
    333             if (DEBUG) Log.d(TAG, "Package not available: " + packageName, e);
    334             else Log.d(TAG, "Package not available: " + packageName);
    335         }
    336         return false;
    337     }
    338 
    339     private void queueMessage(int message) {
    340         synchronized (mQueuedMessages) {
    341             mQueuedMessages.add(message);
    342         }
    343     }
    344 
    345     @Override
    346     public void onTileAdded() {
    347         if (DEBUG) Log.d(TAG, "onTileAdded");
    348         if (mWrapper == null || !mWrapper.onTileAdded()) {
    349             queueMessage(MSG_ON_ADDED);
    350             handleDeath();
    351         }
    352     }
    353 
    354     @Override
    355     public void onTileRemoved() {
    356         if (DEBUG) Log.d(TAG, "onTileRemoved");
    357         if (mWrapper == null || !mWrapper.onTileRemoved()) {
    358             queueMessage(MSG_ON_REMOVED);
    359             handleDeath();
    360         }
    361     }
    362 
    363     @Override
    364     public void onStartListening() {
    365         if (DEBUG) Log.d(TAG, "onStartListening");
    366         mListening = true;
    367         if (mWrapper != null && !mWrapper.onStartListening()) {
    368             handleDeath();
    369         }
    370     }
    371 
    372     @Override
    373     public void onStopListening() {
    374         if (DEBUG) Log.d(TAG, "onStopListening");
    375         mListening = false;
    376         if (mWrapper != null && !mWrapper.onStopListening()) {
    377             handleDeath();
    378         }
    379     }
    380 
    381     @Override
    382     public void onClick(IBinder iBinder) {
    383         if (DEBUG) Log.d(TAG, "onClick " + iBinder + " " + mUser);
    384         if (mWrapper == null || !mWrapper.onClick(iBinder)) {
    385             mClickBinder = iBinder;
    386             queueMessage(MSG_ON_CLICK);
    387             handleDeath();
    388         }
    389     }
    390 
    391     @Override
    392     public void onUnlockComplete() {
    393         if (DEBUG) Log.d(TAG, "onUnlockComplete");
    394         if (mWrapper == null || !mWrapper.onUnlockComplete()) {
    395             queueMessage(MSG_ON_UNLOCK_COMPLETE);
    396             handleDeath();
    397         }
    398     }
    399 
    400     @Override
    401     public IBinder asBinder() {
    402         return mWrapper != null ? mWrapper.asBinder() : null;
    403     }
    404 
    405     @Override
    406     public void binderDied() {
    407         if (DEBUG) Log.d(TAG, "binderDeath");
    408         handleDeath();
    409     }
    410 
    411     public IBinder getToken() {
    412         return mToken;
    413     }
    414 
    415     public interface TileChangeListener {
    416         void onTileChanged(ComponentName tile);
    417     }
    418 
    419     public static boolean isTileAdded(Context context, ComponentName component) {
    420         return context.getSharedPreferences(TILES, 0).getBoolean(component.flattenToString(), false);
    421     }
    422 
    423     public static void setTileAdded(Context context, ComponentName component, boolean added) {
    424         context.getSharedPreferences(TILES, 0).edit().putBoolean(component.flattenToString(),
    425                 added).commit();
    426     }
    427 }
    428