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
17package com.android.systemui.statusbar;
18
19import android.content.BroadcastReceiver;
20import android.content.ComponentName;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.ServiceConnection;
26import android.content.pm.PackageManager;
27import android.database.ContentObserver;
28import android.net.Uri;
29import android.os.Bundle;
30import android.os.Handler;
31import android.os.IBinder;
32import android.os.Message;
33import android.os.RemoteException;
34import android.os.UserHandle;
35import android.provider.Settings;
36import android.util.Log;
37
38import 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 */
49public 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