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 */
onNoService()55         void onNoService();
56         /** The service is about to start, this is a chance to perform cleanup and
57          * delay the start if necessary */
onServiceStartAttempt()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 
onServiceConnected(ComponentName name, IBinder service)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 
onServiceDisconnected(ComponentName name)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 
binderDied()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 
ServiceMonitor(String ownerTag, boolean debug, Context context, String settingKey, Callbacks callbacks)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 
start()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 
getComponentNameFromSetting()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 
packageIntent(Intent intent)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 
stopService()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 
startService()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 
continueStartService()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 
serviceDisconnected(ComponentName serviceName)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 
checkBound()261     private void checkBound() {
262         if (mDebug) Log.d(mTag, "checkBound mBound=" + mBound);
263         if (!mBound) {
264             startService();
265         }
266     }
267 
scheduleCheckBound()268     private void scheduleCheckBound() {
269         mHandler.removeMessages(MSG_CHECK_BOUND);
270         mHandler.sendEmptyMessageDelayed(MSG_CHECK_BOUND, RECHECK_DELAY);
271     }
272 
bundleToString(Bundle bundle)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 
getComponent()285     public ComponentName getComponent() {
286         return getComponentNameFromSetting();
287     }
288 
setComponent(ComponentName component)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 
isPackageAvailable()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