blob: 602989aacadb172e8f9be8c9892efa21977c2b10 [file] [log] [blame]
John Spurlock5c454122013-06-17 07:35:46 -04001/*
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);
John Spurlock4db614d2013-06-27 10:15:19 -0400101 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 }
John Spurlock5c454122013-06-17 07:35:46 -0400106 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);
John Spurlockc72d4cb2015-02-26 14:49:55 -0500180 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
John Spurlock5c454122013-06-17 07:35:46 -0400181 filter.addDataScheme("package");
182 mContext.registerReceiver(mBroadcastReceiver, filter);
183
184 mHandler.sendEmptyMessage(MSG_START_SERVICE);
185 }
186
John Spurlock4db614d2013-06-27 10:15:19 -0400187 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
John Spurlock5c454122013-06-17 07:35:46 -0400193 // 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);
John Spurlockc72d4cb2015-02-26 14:49:55 -0500200 } 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
John Spurlock5c454122013-06-17 07:35:46 -0400206 && pm.getComponentEnabledSetting(mServiceName)
John Spurlockc72d4cb2015-02-26 14:49:55 -0500207 != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
John Spurlock5c454122013-06-17 07:35:46 -0400208 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() {
John Spurlock4db614d2013-06-27 10:15:19 -0400226 mServiceName = getComponentNameFromSetting();
John Spurlock5c454122013-06-17 07:35:46 -0400227 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 }
John Spurlockcdb57ae2015-02-11 19:04:11 -0500284
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 }
John Spurlockc72d4cb2015-02-26 14:49:55 -0500294
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 }
John Spurlock5c454122013-06-17 07:35:46 -0400305}