blob: e98326b620b2d2fb0ec87f647fddfa2c3fe7b5ce [file] [log] [blame]
John Spurlock7340fc82014-04-24 18:50:12 -04001/**
2 * Copyright (c) 2014, 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.server.notification;
18
Julia Reynolds6434eb22016-08-08 17:19:26 -040019import android.app.INotificationManager;
20import android.app.NotificationManager;
John Spurlock3b98b3f2014-05-01 09:08:48 -040021import android.content.ComponentName;
John Spurlock7340fc82014-04-24 18:50:12 -040022import android.content.Context;
Julia Reynoldsb852e562017-06-06 16:14:18 -040023import android.content.pm.IPackageManager;
John Spurlocke77bb362014-04-26 10:24:59 -040024import android.net.Uri;
John Spurlock7340fc82014-04-24 18:50:12 -040025import android.os.IBinder;
26import android.os.IInterface;
John Spurlocke77bb362014-04-26 10:24:59 -040027import android.os.RemoteException;
John Spurlock856edeb2014-06-01 20:36:47 -040028import android.os.UserHandle;
John Spurlock7340fc82014-04-24 18:50:12 -040029import android.provider.Settings;
John Spurlocke77bb362014-04-26 10:24:59 -040030import android.service.notification.Condition;
John Spurlock7340fc82014-04-24 18:50:12 -040031import android.service.notification.ConditionProviderService;
John Spurlocke77bb362014-04-26 10:24:59 -040032import android.service.notification.IConditionProvider;
Jay Aliomer76e1f2722020-03-05 12:36:38 -050033import android.text.TextUtils;
John Spurlocke77bb362014-04-26 10:24:59 -040034import android.util.ArrayMap;
John Spurlock3b98b3f2014-05-01 09:08:48 -040035import android.util.ArraySet;
John Spurlock7340fc82014-04-24 18:50:12 -040036import android.util.Slog;
37
38import com.android.internal.R;
Julia Reynoldsd1bf5f02017-07-11 10:39:58 -040039import com.android.internal.annotations.VisibleForTesting;
John Spurlock25e2d242014-06-27 13:58:23 -040040import com.android.server.notification.NotificationManagerService.DumpFilter;
John Spurlock7340fc82014-04-24 18:50:12 -040041
Jay Aliomer4204f252019-08-26 11:36:53 -040042import org.xmlpull.v1.XmlSerializer;
43
44import java.io.IOException;
John Spurlocke77bb362014-04-26 10:24:59 -040045import java.io.PrintWriter;
John Spurlock3b98b3f2014-05-01 09:08:48 -040046import java.util.ArrayList;
John Spurlocke77bb362014-04-26 10:24:59 -040047import java.util.Arrays;
48
John Spurlock7340fc82014-04-24 18:50:12 -040049public class ConditionProviders extends ManagedServices {
Julia Reynoldsb852e562017-06-06 16:14:18 -040050
Julia Reynoldsd1bf5f02017-07-11 10:39:58 -040051 @VisibleForTesting
52 static final String TAG_ENABLED_DND_APPS = "dnd_apps";
Julia Reynoldsb852e562017-06-06 16:14:18 -040053
John Spurlockb2278d62015-04-07 12:47:12 -040054 private final ArrayList<ConditionRecord> mRecords = new ArrayList<>();
John Spurlockb2278d62015-04-07 12:47:12 -040055 private final ArraySet<String> mSystemConditionProviderNames;
56 private final ArraySet<SystemConditionProviderService> mSystemConditionProviders
57 = new ArraySet<>();
John Spurlockb2278d62015-04-07 12:47:12 -040058 private Callback mCallback;
John Spurlock856edeb2014-06-01 20:36:47 -040059
Julia Reynoldsb852e562017-06-06 16:14:18 -040060 public ConditionProviders(Context context, UserProfiles userProfiles, IPackageManager pm) {
61 super(context, new Object(), userProfiles, pm);
John Spurlockb2278d62015-04-07 12:47:12 -040062 mSystemConditionProviderNames = safeSet(PropConfig.getStringArray(mContext,
John Spurlock530052a2014-11-30 16:26:19 -050063 "system.condition.providers",
64 R.array.config_system_condition_providers));
Julia Reynoldsb852e562017-06-06 16:14:18 -040065 mApprovalLevel = APPROVAL_BY_PACKAGE;
John Spurlock530052a2014-11-30 16:26:19 -050066 }
67
John Spurlockb2278d62015-04-07 12:47:12 -040068 public void setCallback(Callback callback) {
69 mCallback = callback;
70 }
71
72 public boolean isSystemProviderEnabled(String path) {
73 return mSystemConditionProviderNames.contains(path);
74 }
75
76 public void addSystemProvider(SystemConditionProviderService service) {
77 mSystemConditionProviders.add(service);
78 service.attachBase(mContext);
Xiaohui Chenddbe4ca2015-08-13 16:20:56 -070079 registerService(service.asInterface(), service.getComponent(), UserHandle.USER_SYSTEM);
John Spurlockb2278d62015-04-07 12:47:12 -040080 }
81
82 public Iterable<SystemConditionProviderService> getSystemProviders() {
83 return mSystemConditionProviders;
John Spurlock7340fc82014-04-24 18:50:12 -040084 }
85
86 @Override
Jay Aliomer4204f252019-08-26 11:36:53 -040087 protected ArrayMap<Boolean, ArrayList<ComponentName>>
88 resetComponents(String packageName, int userId) {
89 resetPackage(packageName, userId);
90 ArrayMap<Boolean, ArrayList<ComponentName>> changes = new ArrayMap<>();
91 changes.put(true, new ArrayList<>(0));
92 changes.put(false, new ArrayList<>(0));
93 return changes;
94 }
95
96 /**
97 * @return true if the passed package is enabled. false otherwise
98 */
99 boolean resetPackage(String packageName, int userId) {
100 boolean isAllowed = super.isPackageOrComponentAllowed(packageName, userId);
101 boolean isDefault = super.isDefaultComponentOrPackage(packageName);
102 if (!isAllowed && isDefault) {
103 setPackageOrComponentEnabled(packageName, userId, true, true);
104 }
105 if (isAllowed && !isDefault) {
106 setPackageOrComponentEnabled(packageName, userId, true, false);
107 }
108 return !isAllowed && isDefault;
109 }
110
111 @Override
112 void writeDefaults(XmlSerializer out) throws IOException {
113 synchronized (mDefaultsLock) {
114 String defaults = String.join(ENABLED_SERVICES_SEPARATOR, mDefaultPackages);
115 out.attribute(null, ATT_DEFAULTS, defaults);
116 }
117 }
118
119 @Override
John Spurlock7340fc82014-04-24 18:50:12 -0400120 protected Config getConfig() {
John Spurlockb2278d62015-04-07 12:47:12 -0400121 final Config c = new Config();
John Spurlock7340fc82014-04-24 18:50:12 -0400122 c.caption = "condition provider";
123 c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
Julia Reynoldsc279b992015-10-30 08:23:51 -0400124 c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES;
Julia Reynoldsd1bf5f02017-07-11 10:39:58 -0400125 c.xmlTag = TAG_ENABLED_DND_APPS;
Julia Reynolds6e839b02016-04-13 10:01:17 -0400126 c.secondarySettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
John Spurlock7340fc82014-04-24 18:50:12 -0400127 c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
128 c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS;
129 c.clientLabel = R.string.condition_provider_service_binding_label;
130 return c;
131 }
132
133 @Override
John Spurlock25e2d242014-06-27 13:58:23 -0400134 public void dump(PrintWriter pw, DumpFilter filter) {
135 super.dump(pw, filter);
John Spurlocke77bb362014-04-26 10:24:59 -0400136 synchronized(mMutex) {
John Spurlock3b98b3f2014-05-01 09:08:48 -0400137 pw.print(" mRecords("); pw.print(mRecords.size()); pw.println("):");
138 for (int i = 0; i < mRecords.size(); i++) {
John Spurlock856edeb2014-06-01 20:36:47 -0400139 final ConditionRecord r = mRecords.get(i);
John Spurlock25e2d242014-06-27 13:58:23 -0400140 if (filter != null && !filter.matches(r.component)) continue;
John Spurlock856edeb2014-06-01 20:36:47 -0400141 pw.print(" "); pw.println(r);
142 final String countdownDesc = CountdownConditionProvider.tryParseDescription(r.id);
143 if (countdownDesc != null) {
144 pw.print(" ("); pw.print(countdownDesc); pw.println(")");
145 }
John Spurlocke77bb362014-04-26 10:24:59 -0400146 }
147 }
John Spurlockb2278d62015-04-07 12:47:12 -0400148 pw.print(" mSystemConditionProviders: "); pw.println(mSystemConditionProviderNames);
149 for (int i = 0; i < mSystemConditionProviders.size(); i++) {
150 mSystemConditionProviders.valueAt(i).dump(pw, filter);
John Spurlock530052a2014-11-30 16:26:19 -0500151 }
John Spurlocke77bb362014-04-26 10:24:59 -0400152 }
153
154 @Override
John Spurlock7340fc82014-04-24 18:50:12 -0400155 protected IInterface asInterface(IBinder binder) {
156 return IConditionProvider.Stub.asInterface(binder);
157 }
158
159 @Override
Chris Wren51017d02015-12-15 15:34:46 -0500160 protected boolean checkType(IInterface service) {
161 return service instanceof IConditionProvider;
162 }
163
164 @Override
John Spurlock856edeb2014-06-01 20:36:47 -0400165 public void onBootPhaseAppsCanStart() {
166 super.onBootPhaseAppsCanStart();
John Spurlock2f096ed2015-05-04 11:58:26 -0400167 for (int i = 0; i < mSystemConditionProviders.size(); i++) {
168 mSystemConditionProviders.valueAt(i).onBootComplete();
169 }
John Spurlockb2278d62015-04-07 12:47:12 -0400170 if (mCallback != null) {
171 mCallback.onBootComplete();
John Spurlock530052a2014-11-30 16:26:19 -0500172 }
John Spurlock37bc92c2014-11-03 11:01:51 -0500173 }
174
175 @Override
John Spurlock1b8b22b2015-05-20 09:47:13 -0400176 public void onUserSwitched(int user) {
177 super.onUserSwitched(user);
John Spurlockb2278d62015-04-07 12:47:12 -0400178 if (mCallback != null) {
179 mCallback.onUserSwitched();
John Spurlock530052a2014-11-30 16:26:19 -0500180 }
John Spurlock856edeb2014-06-01 20:36:47 -0400181 }
182
183 @Override
John Spurlock3b98b3f2014-05-01 09:08:48 -0400184 protected void onServiceAdded(ManagedServiceInfo info) {
John Spurlock3b98b3f2014-05-01 09:08:48 -0400185 final IConditionProvider provider = provider(info);
John Spurlocke77bb362014-04-26 10:24:59 -0400186 try {
187 provider.onConnected();
188 } catch (RemoteException e) {
Julia Reynolds8f056002018-07-13 15:12:29 -0400189 Slog.e(TAG, "can't connect to service " + info, e);
John Spurlocke77bb362014-04-26 10:24:59 -0400190 // we tried
191 }
John Spurlock39581cc2015-04-10 11:59:01 -0400192 if (mCallback != null) {
193 mCallback.onServiceAdded(info.component);
194 }
John Spurlocke77bb362014-04-26 10:24:59 -0400195 }
196
197 @Override
Jay Aliomer76e1f2722020-03-05 12:36:38 -0500198 protected void loadDefaultsFromConfig() {
199 String defaultDndAccess = mContext.getResources().getString(
200 R.string.config_defaultDndAccessPackages);
201 if (defaultDndAccess != null) {
202 String[] dnds = defaultDndAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR);
203 for (int i = 0; i < dnds.length; i++) {
204 if (TextUtils.isEmpty(dnds[i])) {
205 continue;
206 }
207 addDefaultComponentOrPackage(dnds[i]);
208 }
209 }
210 }
211
212 @Override
John Spurlocke77bb362014-04-26 10:24:59 -0400213 protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
214 if (removed == null) return;
John Spurlock3b98b3f2014-05-01 09:08:48 -0400215 for (int i = mRecords.size() - 1; i >= 0; i--) {
216 final ConditionRecord r = mRecords.get(i);
217 if (!r.component.equals(removed.component)) continue;
John Spurlock3b98b3f2014-05-01 09:08:48 -0400218 mRecords.remove(i);
John Spurlocke77bb362014-04-26 10:24:59 -0400219 }
220 }
221
Julia Reynolds6434eb22016-08-08 17:19:26 -0400222 @Override
Julia Reynoldsb852e562017-06-06 16:14:18 -0400223 public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uid) {
Julia Reynolds6434eb22016-08-08 17:19:26 -0400224 if (removingPackage) {
225 INotificationManager inm = NotificationManager.getService();
226
227 if (pkgList != null && (pkgList.length > 0)) {
228 for (String pkgName : pkgList) {
229 try {
230 inm.removeAutomaticZenRules(pkgName);
231 inm.setNotificationPolicyAccessGranted(pkgName, false);
232 } catch (Exception e) {
233 Slog.e(TAG, "Failed to clean up rules for " + pkgName, e);
234 }
235 }
236 }
237 }
Julia Reynoldsb852e562017-06-06 16:14:18 -0400238 super.onPackagesChanged(removingPackage, pkgList, uid);
Julia Reynolds6434eb22016-08-08 17:19:26 -0400239 }
240
Julia Reynoldse5ce071bf2017-09-15 14:26:32 -0400241 @Override
242 protected boolean isValidEntry(String packageOrComponent, int userId) {
243 return true;
244 }
245
Julia Reynoldsd0ceefa2019-03-03 16:10:52 -0500246 @Override
247 protected String getRequiredPermission() {
248 return null;
249 }
250
John Spurlocke77bb362014-04-26 10:24:59 -0400251 public ManagedServiceInfo checkServiceToken(IConditionProvider provider) {
252 synchronized(mMutex) {
253 return checkServiceTokenLocked(provider);
254 }
255 }
256
Julia Reynolds1d6d16d2016-03-07 13:51:02 -0500257 private Condition[] removeDuplicateConditions(String pkg, Condition[] conditions) {
John Spurlock3b98b3f2014-05-01 09:08:48 -0400258 if (conditions == null || conditions.length == 0) return null;
259 final int N = conditions.length;
260 final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N);
261 for (int i = 0; i < N; i++) {
262 final Uri id = conditions[i].id;
John Spurlock3b98b3f2014-05-01 09:08:48 -0400263 if (valid.containsKey(id)) {
264 Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id);
265 continue;
266 }
267 valid.put(id, conditions[i]);
268 }
269 if (valid.size() == 0) return null;
270 if (valid.size() == N) return conditions;
271 final Condition[] rt = new Condition[valid.size()];
272 for (int i = 0; i < rt.length; i++) {
273 rt[i] = valid.valueAt(i);
274 }
275 return rt;
276 }
277
John Spurlockb2278d62015-04-07 12:47:12 -0400278 private ConditionRecord getRecordLocked(Uri id, ComponentName component, boolean create) {
279 if (id == null || component == null) return null;
John Spurlock3b98b3f2014-05-01 09:08:48 -0400280 final int N = mRecords.size();
281 for (int i = 0; i < N; i++) {
282 final ConditionRecord r = mRecords.get(i);
283 if (r.id.equals(id) && r.component.equals(component)) {
284 return r;
285 }
286 }
John Spurlockb2278d62015-04-07 12:47:12 -0400287 if (create) {
288 final ConditionRecord r = new ConditionRecord(id, component);
289 mRecords.add(r);
290 return r;
291 }
292 return null;
John Spurlock3b98b3f2014-05-01 09:08:48 -0400293 }
294
John Spurlocke77bb362014-04-26 10:24:59 -0400295 public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
296 synchronized(mMutex) {
297 if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
298 + (conditions == null ? null : Arrays.asList(conditions)));
Julia Reynolds1d6d16d2016-03-07 13:51:02 -0500299 conditions = removeDuplicateConditions(pkg, conditions);
John Spurlocke77bb362014-04-26 10:24:59 -0400300 if (conditions == null || conditions.length == 0) return;
301 final int N = conditions.length;
John Spurlock3b98b3f2014-05-01 09:08:48 -0400302 for (int i = 0; i < N; i++) {
303 final Condition c = conditions[i];
John Spurlockb2278d62015-04-07 12:47:12 -0400304 final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/);
John Spurlock3b98b3f2014-05-01 09:08:48 -0400305 r.info = info;
306 r.condition = c;
Julia Reynolds0f50e4e2016-02-11 08:54:24 -0500307 }
308 }
309 final int N = conditions.length;
310 for (int i = 0; i < N; i++) {
311 final Condition c = conditions[i];
312 if (mCallback != null) {
313 mCallback.onConditionChanged(c.id, c);
John Spurlocke77bb362014-04-26 10:24:59 -0400314 }
315 }
316 }
317
John Spurlock39581cc2015-04-10 11:59:01 -0400318 public IConditionProvider findConditionProvider(ComponentName component) {
319 if (component == null) return null;
Julia Reynolds00314d92017-04-14 10:01:24 -0400320 for (ManagedServiceInfo service : getServices()) {
John Spurlock39581cc2015-04-10 11:59:01 -0400321 if (component.equals(service.component)) {
322 return provider(service);
323 }
324 }
325 return null;
326 }
327
John Spurlock21258a32015-05-27 18:22:55 -0400328 public Condition findCondition(ComponentName component, Uri conditionId) {
329 if (component == null || conditionId == null) return null;
330 synchronized (mMutex) {
331 final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
332 return r != null ? r.condition : null;
333 }
334 }
335
John Spurlockb2278d62015-04-07 12:47:12 -0400336 public void ensureRecordExists(ComponentName component, Uri conditionId,
337 IConditionProvider provider) {
Julia Reynolds3ff1fd92018-08-29 09:57:11 -0400338 synchronized (mMutex) {
339 // constructed by convention, make sure the record exists...
340 final ConditionRecord r = getRecordLocked(conditionId, component, true /*create*/);
341 if (r.info == null) {
342 // ... and is associated with the in-process service
343 r.info = checkServiceTokenLocked(provider);
344 }
John Spurlock530052a2014-11-30 16:26:19 -0500345 }
346 }
347
John Spurlockb2278d62015-04-07 12:47:12 -0400348 public boolean subscribeIfNecessary(ComponentName component, Uri conditionId) {
349 synchronized (mMutex) {
350 final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
351 if (r == null) {
352 Slog.w(TAG, "Unable to subscribe to " + component + " " + conditionId);
353 return false;
John Spurlock856edeb2014-06-01 20:36:47 -0400354 }
John Spurlockb2278d62015-04-07 12:47:12 -0400355 if (r.subscribed) return true;
356 subscribeLocked(r);
357 return r.subscribed;
358 }
359 }
360
361 public void unsubscribeIfNecessary(ComponentName component, Uri conditionId) {
362 synchronized (mMutex) {
363 final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
364 if (r == null) {
365 Slog.w(TAG, "Unable to unsubscribe to " + component + " " + conditionId);
366 return;
John Spurlocke77bb362014-04-26 10:24:59 -0400367 }
John Spurlockb2278d62015-04-07 12:47:12 -0400368 if (!r.subscribed) return;
369 unsubscribeLocked(r);;
John Spurlocke77bb362014-04-26 10:24:59 -0400370 }
371 }
372
John Spurlock3b98b3f2014-05-01 09:08:48 -0400373 private void subscribeLocked(ConditionRecord r) {
374 if (DEBUG) Slog.d(TAG, "subscribeLocked " + r);
375 final IConditionProvider provider = provider(r);
John Spurlock6ae82a72014-07-16 16:23:01 -0400376 RemoteException re = null;
377 if (provider != null) {
378 try {
John Spurlockb2278d62015-04-07 12:47:12 -0400379 Slog.d(TAG, "Subscribing to " + r.id + " with " + r.component);
John Spurlock6ae82a72014-07-16 16:23:01 -0400380 provider.onSubscribe(r.id);
John Spurlockb2278d62015-04-07 12:47:12 -0400381 r.subscribed = true;
John Spurlock6ae82a72014-07-16 16:23:01 -0400382 } catch (RemoteException e) {
383 Slog.w(TAG, "Error subscribing to " + r, e);
384 re = e;
385 }
John Spurlocke77bb362014-04-26 10:24:59 -0400386 }
John Spurlock6ae82a72014-07-16 16:23:01 -0400387 ZenLog.traceSubscribe(r != null ? r.id : null, provider, re);
John Spurlock3b98b3f2014-05-01 09:08:48 -0400388 }
389
John Spurlock530052a2014-11-30 16:26:19 -0500390 @SafeVarargs
John Spurlock3b98b3f2014-05-01 09:08:48 -0400391 private static <T> ArraySet<T> safeSet(T... items) {
392 final ArraySet<T> rt = new ArraySet<T>();
393 if (items == null || items.length == 0) return rt;
394 final int N = items.length;
395 for (int i = 0; i < N; i++) {
396 final T item = items[i];
397 if (item != null) {
398 rt.add(item);
399 }
400 }
401 return rt;
402 }
403
John Spurlock3b98b3f2014-05-01 09:08:48 -0400404 private void unsubscribeLocked(ConditionRecord r) {
405 if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
406 final IConditionProvider provider = provider(r);
John Spurlock6ae82a72014-07-16 16:23:01 -0400407 RemoteException re = null;
408 if (provider != null) {
409 try {
410 provider.onUnsubscribe(r.id);
411 } catch (RemoteException e) {
412 Slog.w(TAG, "Error unsubscribing to " + r, e);
413 re = e;
414 }
John Spurlockb2278d62015-04-07 12:47:12 -0400415 r.subscribed = false;
John Spurlock3b98b3f2014-05-01 09:08:48 -0400416 }
John Spurlock6ae82a72014-07-16 16:23:01 -0400417 ZenLog.traceUnsubscribe(r != null ? r.id : null, provider, re);
John Spurlock3b98b3f2014-05-01 09:08:48 -0400418 }
419
420 private static IConditionProvider provider(ConditionRecord r) {
421 return r == null ? null : provider(r.info);
John Spurlocke77bb362014-04-26 10:24:59 -0400422 }
423
424 private static IConditionProvider provider(ManagedServiceInfo info) {
425 return info == null ? null : (IConditionProvider) info.service;
426 }
427
John Spurlock3b98b3f2014-05-01 09:08:48 -0400428 private static class ConditionRecord {
429 public final Uri id;
430 public final ComponentName component;
431 public Condition condition;
432 public ManagedServiceInfo info;
John Spurlockb2278d62015-04-07 12:47:12 -0400433 public boolean subscribed;
John Spurlock3b98b3f2014-05-01 09:08:48 -0400434
435 private ConditionRecord(Uri id, ComponentName component) {
436 this.id = id;
437 this.component = component;
438 }
439
440 @Override
441 public String toString() {
442 final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
John Spurlockb2278d62015-04-07 12:47:12 -0400443 .append(id).append(",component=").append(component)
444 .append(",subscribed=").append(subscribed);
John Spurlock3b98b3f2014-05-01 09:08:48 -0400445 return sb.append(']').toString();
446 }
447 }
John Spurlockb2278d62015-04-07 12:47:12 -0400448
449 public interface Callback {
450 void onBootComplete();
John Spurlock39581cc2015-04-10 11:59:01 -0400451 void onServiceAdded(ComponentName component);
John Spurlockb2278d62015-04-07 12:47:12 -0400452 void onConditionChanged(Uri id, Condition condition);
453 void onUserSwitched();
454 }
455
John Spurlock7340fc82014-04-24 18:50:12 -0400456}