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