2 * Copyright (c) 2014, The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server.notification;
19 import android.annotation.NonNull;
20 import android.content.ComponentName;
21 import android.content.ContentResolver;
22 import android.content.Context;
23 import android.net.Uri;
24 import android.os.Handler;
25 import android.os.IBinder;
26 import android.os.IInterface;
27 import android.os.RemoteException;
28 import android.os.UserHandle;
29 import android.provider.Settings;
30 import android.service.notification.Condition;
31 import android.service.notification.ConditionProviderService;
32 import android.service.notification.IConditionProvider;
33 import android.text.TextUtils;
34 import android.util.ArrayMap;
35 import android.util.ArraySet;
36 import android.util.Slog;
38 import com.android.internal.R;
39 import com.android.server.notification.NotificationManagerService.DumpFilter;
41 import java.io.PrintWriter;
42 import java.util.ArrayList;
43 import java.util.Arrays;
45 public class ConditionProviders extends ManagedServices {
46 private final ArrayList<ConditionRecord> mRecords = new ArrayList<>();
47 private final ArraySet<String> mSystemConditionProviderNames;
48 private final ArraySet<SystemConditionProviderService> mSystemConditionProviders
51 private Callback mCallback;
53 public ConditionProviders(Context context, Handler handler, UserProfiles userProfiles) {
54 super(context, handler, new Object(), userProfiles);
55 mSystemConditionProviderNames = safeSet(PropConfig.getStringArray(mContext,
56 "system.condition.providers",
57 R.array.config_system_condition_providers));
60 public void setCallback(Callback callback) {
64 public boolean isSystemProviderEnabled(String path) {
65 return mSystemConditionProviderNames.contains(path);
68 public void addSystemProvider(SystemConditionProviderService service) {
69 mSystemConditionProviders.add(service);
70 service.attachBase(mContext);
71 registerService(service.asInterface(), service.getComponent(), UserHandle.USER_SYSTEM);
74 public Iterable<SystemConditionProviderService> getSystemProviders() {
75 return mSystemConditionProviders;
79 protected Config getConfig() {
80 final Config c = new Config();
81 c.caption = "condition provider";
82 c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
83 c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES;
84 c.secondarySettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
85 c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
86 c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS;
87 c.clientLabel = R.string.condition_provider_service_binding_label;
92 public void dump(PrintWriter pw, DumpFilter filter) {
93 super.dump(pw, filter);
94 synchronized(mMutex) {
95 pw.print(" mRecords("); pw.print(mRecords.size()); pw.println("):");
96 for (int i = 0; i < mRecords.size(); i++) {
97 final ConditionRecord r = mRecords.get(i);
98 if (filter != null && !filter.matches(r.component)) continue;
99 pw.print(" "); pw.println(r);
100 final String countdownDesc = CountdownConditionProvider.tryParseDescription(r.id);
101 if (countdownDesc != null) {
102 pw.print(" ("); pw.print(countdownDesc); pw.println(")");
106 pw.print(" mSystemConditionProviders: "); pw.println(mSystemConditionProviderNames);
107 for (int i = 0; i < mSystemConditionProviders.size(); i++) {
108 mSystemConditionProviders.valueAt(i).dump(pw, filter);
113 protected IInterface asInterface(IBinder binder) {
114 return IConditionProvider.Stub.asInterface(binder);
118 protected boolean checkType(IInterface service) {
119 return service instanceof IConditionProvider;
123 public void onBootPhaseAppsCanStart() {
124 super.onBootPhaseAppsCanStart();
125 for (int i = 0; i < mSystemConditionProviders.size(); i++) {
126 mSystemConditionProviders.valueAt(i).onBootComplete();
128 if (mCallback != null) {
129 mCallback.onBootComplete();
134 public void onUserSwitched(int user) {
135 super.onUserSwitched(user);
136 if (mCallback != null) {
137 mCallback.onUserSwitched();
142 protected void onServiceAdded(ManagedServiceInfo info) {
143 final IConditionProvider provider = provider(info);
145 provider.onConnected();
146 } catch (RemoteException e) {
149 if (mCallback != null) {
150 mCallback.onServiceAdded(info.component);
155 protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
156 if (removed == null) return;
157 for (int i = mRecords.size() - 1; i >= 0; i--) {
158 final ConditionRecord r = mRecords.get(i);
159 if (!r.component.equals(removed.component)) continue;
164 public ManagedServiceInfo checkServiceToken(IConditionProvider provider) {
165 synchronized(mMutex) {
166 return checkServiceTokenLocked(provider);
170 private Condition[] removeDuplicateConditions(String pkg, Condition[] conditions) {
171 if (conditions == null || conditions.length == 0) return null;
172 final int N = conditions.length;
173 final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N);
174 for (int i = 0; i < N; i++) {
175 final Uri id = conditions[i].id;
176 if (valid.containsKey(id)) {
177 Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id);
180 valid.put(id, conditions[i]);
182 if (valid.size() == 0) return null;
183 if (valid.size() == N) return conditions;
184 final Condition[] rt = new Condition[valid.size()];
185 for (int i = 0; i < rt.length; i++) {
186 rt[i] = valid.valueAt(i);
191 private ConditionRecord getRecordLocked(Uri id, ComponentName component, boolean create) {
192 if (id == null || component == null) return null;
193 final int N = mRecords.size();
194 for (int i = 0; i < N; i++) {
195 final ConditionRecord r = mRecords.get(i);
196 if (r.id.equals(id) && r.component.equals(component)) {
201 final ConditionRecord r = new ConditionRecord(id, component);
208 public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
209 synchronized(mMutex) {
210 if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
211 + (conditions == null ? null : Arrays.asList(conditions)));
212 conditions = removeDuplicateConditions(pkg, conditions);
213 if (conditions == null || conditions.length == 0) return;
214 final int N = conditions.length;
215 for (int i = 0; i < N; i++) {
216 final Condition c = conditions[i];
217 final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/);
222 final int N = conditions.length;
223 for (int i = 0; i < N; i++) {
224 final Condition c = conditions[i];
225 if (mCallback != null) {
226 mCallback.onConditionChanged(c.id, c);
231 public IConditionProvider findConditionProvider(ComponentName component) {
232 if (component == null) return null;
233 for (ManagedServiceInfo service : mServices) {
234 if (component.equals(service.component)) {
235 return provider(service);
241 public Condition findCondition(ComponentName component, Uri conditionId) {
242 if (component == null || conditionId == null) return null;
243 synchronized (mMutex) {
244 final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
245 return r != null ? r.condition : null;
249 public void ensureRecordExists(ComponentName component, Uri conditionId,
250 IConditionProvider provider) {
251 // constructed by convention, make sure the record exists...
252 final ConditionRecord r = getRecordLocked(conditionId, component, true /*create*/);
253 if (r.info == null) {
254 // ... and is associated with the in-process service
255 r.info = checkServiceTokenLocked(provider);
260 protected @NonNull ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName,
262 final ContentResolver cr = mContext.getContentResolver();
263 String settingValue = Settings.Secure.getStringForUser(
267 if (TextUtils.isEmpty(settingValue))
268 return new ArraySet<>();
269 String[] packages = settingValue.split(ENABLED_SERVICES_SEPARATOR);
270 ArraySet<ComponentName> result = new ArraySet<>(packages.length);
271 for (int i = 0; i < packages.length; i++) {
272 if (!TextUtils.isEmpty(packages[i])) {
273 final ComponentName component = ComponentName.unflattenFromString(packages[i]);
274 if (component != null) {
275 result.addAll(queryPackageForServices(component.getPackageName(), userId));
277 result.addAll(queryPackageForServices(packages[i], userId));
284 public boolean subscribeIfNecessary(ComponentName component, Uri conditionId) {
285 synchronized (mMutex) {
286 final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
288 Slog.w(TAG, "Unable to subscribe to " + component + " " + conditionId);
291 if (r.subscribed) return true;
297 public void unsubscribeIfNecessary(ComponentName component, Uri conditionId) {
298 synchronized (mMutex) {
299 final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
301 Slog.w(TAG, "Unable to unsubscribe to " + component + " " + conditionId);
304 if (!r.subscribed) return;
305 unsubscribeLocked(r);;
309 private void subscribeLocked(ConditionRecord r) {
310 if (DEBUG) Slog.d(TAG, "subscribeLocked " + r);
311 final IConditionProvider provider = provider(r);
312 RemoteException re = null;
313 if (provider != null) {
315 Slog.d(TAG, "Subscribing to " + r.id + " with " + r.component);
316 provider.onSubscribe(r.id);
318 } catch (RemoteException e) {
319 Slog.w(TAG, "Error subscribing to " + r, e);
323 ZenLog.traceSubscribe(r != null ? r.id : null, provider, re);
327 private static <T> ArraySet<T> safeSet(T... items) {
328 final ArraySet<T> rt = new ArraySet<T>();
329 if (items == null || items.length == 0) return rt;
330 final int N = items.length;
331 for (int i = 0; i < N; i++) {
332 final T item = items[i];
340 private void unsubscribeLocked(ConditionRecord r) {
341 if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
342 final IConditionProvider provider = provider(r);
343 RemoteException re = null;
344 if (provider != null) {
346 provider.onUnsubscribe(r.id);
347 } catch (RemoteException e) {
348 Slog.w(TAG, "Error unsubscribing to " + r, e);
351 r.subscribed = false;
353 ZenLog.traceUnsubscribe(r != null ? r.id : null, provider, re);
356 private static IConditionProvider provider(ConditionRecord r) {
357 return r == null ? null : provider(r.info);
360 private static IConditionProvider provider(ManagedServiceInfo info) {
361 return info == null ? null : (IConditionProvider) info.service;
364 private static class ConditionRecord {
366 public final ComponentName component;
367 public Condition condition;
368 public ManagedServiceInfo info;
369 public boolean subscribed;
371 private ConditionRecord(Uri id, ComponentName component) {
373 this.component = component;
377 public String toString() {
378 final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
379 .append(id).append(",component=").append(component)
380 .append(",subscribed=").append(subscribed);
381 return sb.append(']').toString();
385 public interface Callback {
386 void onBootComplete();
387 void onServiceAdded(ComponentName component);
388 void onConditionChanged(Uri id, Condition condition);
389 void onUserSwitched();