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.addDataScheme("package");
181        mContext.registerReceiver(mBroadcastReceiver, filter);
182
183        mHandler.sendEmptyMessage(MSG_START_SERVICE);
184    }
185
186    private ComponentName getComponentNameFromSetting() {
187        String cn = Settings.Secure.getStringForUser(mContext.getContentResolver(),
188                mSettingKey, UserHandle.USER_CURRENT);
189        return cn == null ? null : ComponentName.unflattenFromString(cn);
190    }
191
192    // everything below is called on the handler
193
194    private void packageIntent(Intent intent) {
195        if (mDebug) Log.d(mTag, "packageIntent intent=" + intent
196                + " extras=" + bundleToString(intent.getExtras()));
197        if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
198            mHandler.sendEmptyMessage(MSG_START_SERVICE);
199        } else if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())) {
200            PackageManager pm = mContext.getPackageManager();
201            boolean serviceEnabled =
202                    pm.getApplicationEnabledSetting(mServiceName.getPackageName())
203                        != PackageManager.COMPONENT_ENABLED_STATE_DISABLED
204                    && pm.getComponentEnabledSetting(mServiceName)
205                        != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
206            if (mBound && !serviceEnabled) {
207                stopService();
208                scheduleCheckBound();
209            } else if (!mBound && serviceEnabled) {
210                startService();
211            }
212        }
213    }
214
215    private void stopService() {
216        if (mDebug) Log.d(mTag, "stopService");
217        boolean stopped = mContext.stopService(new Intent().setComponent(mServiceName));
218        if (mDebug) Log.d(mTag, "  stopped=" + stopped);
219        mContext.unbindService(mServiceConnection);
220        mBound = false;
221    }
222
223    private void startService() {
224        mServiceName = getComponentNameFromSetting();
225        if (mDebug) Log.d(mTag, "startService mServiceName=" + mServiceName);
226        if (mServiceName == null) {
227            mBound = false;
228            mCallbacks.onNoService();
229        } else {
230            long delay = mCallbacks.onServiceStartAttempt();
231            mHandler.sendEmptyMessageDelayed(MSG_CONTINUE_START_SERVICE, delay);
232        }
233    }
234
235    private void continueStartService() {
236        if (mDebug) Log.d(mTag, "continueStartService");
237        Intent intent = new Intent().setComponent(mServiceName);
238        try {
239            mServiceConnection = new SC();
240            mBound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
241            if (mDebug) Log.d(mTag, "mBound: " + mBound);
242        } catch (Throwable t) {
243            Log.w(mTag, "Error binding to service: " + mServiceName, t);
244        }
245        if (!mBound) {
246            mCallbacks.onNoService();
247        }
248    }
249
250    private void serviceDisconnected(ComponentName serviceName) {
251        if (mDebug) Log.d(mTag, "serviceDisconnected serviceName=" + serviceName
252                + " mServiceName=" + mServiceName);
253        if (serviceName.equals(mServiceName)) {
254            mBound = false;
255            scheduleCheckBound();
256        }
257    }
258
259    private void checkBound() {
260        if (mDebug) Log.d(mTag, "checkBound mBound=" + mBound);
261        if (!mBound) {
262            startService();
263        }
264    }
265
266    private void scheduleCheckBound() {
267        mHandler.removeMessages(MSG_CHECK_BOUND);
268        mHandler.sendEmptyMessageDelayed(MSG_CHECK_BOUND, RECHECK_DELAY);
269    }
270
271    private static String bundleToString(Bundle bundle) {
272        if (bundle == null) return null;
273        StringBuilder sb = new StringBuilder('{');
274        for (String key : bundle.keySet()) {
275            if (sb.length() > 1) sb.append(',');
276            Object v = bundle.get(key);
277            v = (v instanceof String[]) ? Arrays.asList((String[]) v) : v;
278            sb.append(key).append('=').append(v);
279        }
280        return sb.append('}').toString();
281    }
282}
283