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