Home | History | Annotate | Download | only in statusbar
      1 /*
      2  * Copyright (C) 2013 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 
     17 package com.android.systemui.statusbar;
     18 
     19 import android.content.BroadcastReceiver;
     20 import android.content.ComponentName;
     21 import android.content.ContentResolver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.content.ServiceConnection;
     26 import android.content.pm.PackageManager;
     27 import android.database.ContentObserver;
     28 import android.net.Uri;
     29 import android.os.Bundle;
     30 import android.os.Handler;
     31 import android.os.IBinder;
     32 import android.os.Message;
     33 import android.os.RemoteException;
     34 import android.os.UserHandle;
     35 import android.provider.Settings;
     36 import android.util.Log;
     37 
     38 import java.util.Arrays;
     39 
     40 /**
     41  * Manages a persistent connection to a service component defined in a secure setting.
     42  *
     43  * <p>If a valid service component is specified in the secure setting, starts it up and keeps it
     44  * running; handling setting changes, package updates, component disabling, and unexpected
     45  * process termination.
     46  *
     47  * <p>Clients can listen for important events using the supplied {@link Callbacks}.
     48  */
     49 public class ServiceMonitor {
     50     private static final int RECHECK_DELAY = 2000;
     51     private static final int WAIT_FOR_STOP = 500;
     52 
     53     public interface Callbacks {
     54         /** The service does not exist or failed to bind */
     55         void onNoService();
     56         /** The service is about to start, this is a chance to perform cleanup and
     57          * delay the start if necessary */
     58         long onServiceStartAttempt();
     59     }
     60 
     61     // internal handler + messages used to serialize access to internal state
     62     public static final int MSG_START_SERVICE = 1;
     63     public static final int MSG_CONTINUE_START_SERVICE = 2;
     64     public static final int MSG_STOP_SERVICE = 3;
     65     public static final int MSG_PACKAGE_INTENT = 4;
     66     public static final int MSG_CHECK_BOUND = 5;
     67     public static final int MSG_SERVICE_DISCONNECTED = 6;
     68 
     69     private final Handler mHandler = new Handler() {
     70         public void handleMessage(Message msg) {
     71             switch(msg.what) {
     72                 case MSG_START_SERVICE:
     73                     startService();
     74                     break;
     75                 case MSG_CONTINUE_START_SERVICE:
     76                     continueStartService();
     77                     break;
     78                 case MSG_STOP_SERVICE:
     79                     stopService();
     80                     break;
     81                 case MSG_PACKAGE_INTENT:
     82                     packageIntent((Intent)msg.obj);
     83                     break;
     84                 case MSG_CHECK_BOUND:
     85                     checkBound();
     86                     break;
     87                 case MSG_SERVICE_DISCONNECTED:
     88                     serviceDisconnected((ComponentName)msg.obj);
     89                     break;
     90             }
     91         }
     92     };
     93 
     94     private final ContentObserver mSettingObserver = new ContentObserver(mHandler) {
     95         public void onChange(boolean selfChange) {
     96             onChange(selfChange, null);
     97         }
     98 
     99         public void onChange(boolean selfChange, Uri uri) {
    100             if (mDebug) Log.d(mTag, "onChange selfChange=" + selfChange + " uri=" + uri);
    101             ComponentName cn = getComponentNameFromSetting();
    102             if (cn == null && mServiceName == null || cn != null && cn.equals(mServiceName)) {
    103                 if (mDebug) Log.d(mTag, "skipping no-op restart");
    104                 return;
    105             }
    106             if (mBound) {
    107                 mHandler.sendEmptyMessage(MSG_STOP_SERVICE);
    108             }
    109             mHandler.sendEmptyMessageDelayed(MSG_START_SERVICE, WAIT_FOR_STOP);
    110         }
    111     };
    112 
    113     private final class SC implements ServiceConnection, IBinder.DeathRecipient {
    114         private ComponentName mName;
    115         private IBinder mService;
    116 
    117         public void onServiceConnected(ComponentName name, IBinder service) {
    118             if (mDebug) Log.d(mTag, "onServiceConnected name=" + name + " service=" + service);
    119             mName = name;
    120             mService = service;
    121             try {
    122                 service.linkToDeath(this, 0);
    123             } catch (RemoteException e) {
    124                 Log.w(mTag, "Error linking to death", e);
    125             }
    126         }
    127 
    128         public void onServiceDisconnected(ComponentName name) {
    129             if (mDebug) Log.d(mTag, "onServiceDisconnected name=" + name);
    130             boolean unlinked = mService.unlinkToDeath(this, 0);
    131             if (mDebug) Log.d(mTag, "  unlinked=" + unlinked);
    132             mHandler.sendMessage(mHandler.obtainMessage(MSG_SERVICE_DISCONNECTED, mName));
    133         }
    134 
    135         public void binderDied() {
    136             if (mDebug) Log.d(mTag, "binderDied");
    137             mHandler.sendMessage(mHandler.obtainMessage(MSG_SERVICE_DISCONNECTED, mName));
    138         }
    139     }
    140 
    141     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    142         public void onReceive(Context context, Intent intent) {
    143             String pkg = intent.getData().getSchemeSpecificPart();
    144             if (mServiceName != null && mServiceName.getPackageName().equals(pkg)) {
    145                 mHandler.sendMessage(mHandler.obtainMessage(MSG_PACKAGE_INTENT, intent));
    146             }
    147         }
    148     };
    149 
    150     private final String mTag;
    151     private final boolean mDebug;
    152 
    153     private final Context mContext;
    154     private final String mSettingKey;
    155     private final Callbacks mCallbacks;
    156 
    157     private ComponentName mServiceName;
    158     private SC mServiceConnection;
    159     private boolean mBound;
    160 
    161     public ServiceMonitor(String ownerTag, boolean debug,
    162             Context context, String settingKey, Callbacks callbacks) {
    163         mTag = ownerTag + ".ServiceMonitor";
    164         mDebug = debug;
    165         mContext = context;
    166         mSettingKey = settingKey;
    167         mCallbacks = callbacks;
    168     }
    169 
    170     public void start() {
    171         // listen for setting changes
    172         ContentResolver cr = mContext.getContentResolver();
    173         cr.registerContentObserver(Settings.Secure.getUriFor(mSettingKey),
    174                 false /*notifyForDescendents*/, mSettingObserver, UserHandle.USER_ALL);
    175 
    176         // listen for package/component changes
    177         IntentFilter filter = new IntentFilter();
    178         filter.addAction(Intent.ACTION_PACKAGE_ADDED);
    179         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
    180         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
    181         filter.addDataScheme("package");
    182         mContext.registerReceiver(mBroadcastReceiver, filter);
    183 
    184         mHandler.sendEmptyMessage(MSG_START_SERVICE);
    185     }
    186 
    187     private ComponentName getComponentNameFromSetting() {
    188         String cn = Settings.Secure.getStringForUser(mContext.getContentResolver(),
    189                 mSettingKey, UserHandle.USER_CURRENT);
    190         return cn == null ? null : ComponentName.unflattenFromString(cn);
    191     }
    192 
    193     // everything below is called on the handler
    194 
    195     private void packageIntent(Intent intent) {
    196         if (mDebug) Log.d(mTag, "packageIntent intent=" + intent
    197                 + " extras=" + bundleToString(intent.getExtras()));
    198         if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
    199             mHandler.sendEmptyMessage(MSG_START_SERVICE);
    200         } else if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())
    201                 || Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
    202             final PackageManager pm = mContext.getPackageManager();
    203             final boolean serviceEnabled = isPackageAvailable()
    204                     && pm.getApplicationEnabledSetting(mServiceName.getPackageName())
    205                             != PackageManager.COMPONENT_ENABLED_STATE_DISABLED
    206                     && pm.getComponentEnabledSetting(mServiceName)
    207                             != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
    208             if (mBound && !serviceEnabled) {
    209                 stopService();
    210                 scheduleCheckBound();
    211             } else if (!mBound && serviceEnabled) {
    212                 startService();
    213             }
    214         }
    215     }
    216 
    217     private void stopService() {
    218         if (mDebug) Log.d(mTag, "stopService");
    219         boolean stopped = mContext.stopService(new Intent().setComponent(mServiceName));
    220         if (mDebug) Log.d(mTag, "  stopped=" + stopped);
    221         mContext.unbindService(mServiceConnection);
    222         mBound = false;
    223     }
    224 
    225     private void startService() {
    226         mServiceName = getComponentNameFromSetting();
    227         if (mDebug) Log.d(mTag, "startService mServiceName=" + mServiceName);
    228         if (mServiceName == null) {
    229             mBound = false;
    230             mCallbacks.onNoService();
    231         } else {
    232             long delay = mCallbacks.onServiceStartAttempt();
    233             mHandler.sendEmptyMessageDelayed(MSG_CONTINUE_START_SERVICE, delay);
    234         }
    235     }
    236 
    237     private void continueStartService() {
    238         if (mDebug) Log.d(mTag, "continueStartService");
    239         Intent intent = new Intent().setComponent(mServiceName);
    240         try {
    241             mServiceConnection = new SC();
    242             mBound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    243             if (mDebug) Log.d(mTag, "mBound: " + mBound);
    244         } catch (Throwable t) {
    245             Log.w(mTag, "Error binding to service: " + mServiceName, t);
    246         }
    247         if (!mBound) {
    248             mCallbacks.onNoService();
    249         }
    250     }
    251 
    252     private void serviceDisconnected(ComponentName serviceName) {
    253         if (mDebug) Log.d(mTag, "serviceDisconnected serviceName=" + serviceName
    254                 + " mServiceName=" + mServiceName);
    255         if (serviceName.equals(mServiceName)) {
    256             mBound = false;
    257             scheduleCheckBound();
    258         }
    259     }
    260 
    261     private void checkBound() {
    262         if (mDebug) Log.d(mTag, "checkBound mBound=" + mBound);
    263         if (!mBound) {
    264             startService();
    265         }
    266     }
    267 
    268     private void scheduleCheckBound() {
    269         mHandler.removeMessages(MSG_CHECK_BOUND);
    270         mHandler.sendEmptyMessageDelayed(MSG_CHECK_BOUND, RECHECK_DELAY);
    271     }
    272 
    273     private static String bundleToString(Bundle bundle) {
    274         if (bundle == null) return null;
    275         StringBuilder sb = new StringBuilder('{');
    276         for (String key : bundle.keySet()) {
    277             if (sb.length() > 1) sb.append(',');
    278             Object v = bundle.get(key);
    279             v = (v instanceof String[]) ? Arrays.asList((String[]) v) : v;
    280             sb.append(key).append('=').append(v);
    281         }
    282         return sb.append('}').toString();
    283     }
    284 
    285     public ComponentName getComponent() {
    286         return getComponentNameFromSetting();
    287     }
    288 
    289     public void setComponent(ComponentName component) {
    290         final String setting = component == null ? null : component.flattenToShortString();
    291         Settings.Secure.putStringForUser(mContext.getContentResolver(),
    292                 mSettingKey, setting, UserHandle.USER_CURRENT);
    293     }
    294 
    295     public boolean isPackageAvailable() {
    296         final ComponentName component = getComponent();
    297         if (component == null) return false;
    298         try {
    299             return mContext.getPackageManager().isPackageAvailable(component.getPackageName());
    300         } catch (RuntimeException e) {
    301             Log.w(mTag, "Error checking package availability", e);
    302             return false;
    303         }
    304     }
    305 }
    306