• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
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 
17 package com.android.server.notification;
18 
19 import android.app.INotificationManager;
20 import android.app.NotificationManager;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.pm.IPackageManager;
24 import android.net.Uri;
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.util.ArrayMap;
34 import android.util.ArraySet;
35 import android.util.Slog;
36 
37 import com.android.internal.R;
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.server.notification.NotificationManagerService.DumpFilter;
40 
41 import java.io.PrintWriter;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 
45 public class ConditionProviders extends ManagedServices {
46 
47     @VisibleForTesting
48     static final String TAG_ENABLED_DND_APPS = "dnd_apps";
49 
50     private final ArrayList<ConditionRecord> mRecords = new ArrayList<>();
51     private final ArraySet<String> mSystemConditionProviderNames;
52     private final ArraySet<SystemConditionProviderService> mSystemConditionProviders
53             = new ArraySet<>();
54 
55     private Callback mCallback;
56 
ConditionProviders(Context context, UserProfiles userProfiles, IPackageManager pm)57     public ConditionProviders(Context context, UserProfiles userProfiles, IPackageManager pm) {
58         super(context, new Object(), userProfiles, pm);
59         mSystemConditionProviderNames = safeSet(PropConfig.getStringArray(mContext,
60                 "system.condition.providers",
61                 R.array.config_system_condition_providers));
62         mApprovalLevel = APPROVAL_BY_PACKAGE;
63     }
64 
setCallback(Callback callback)65     public void setCallback(Callback callback) {
66         mCallback = callback;
67     }
68 
isSystemProviderEnabled(String path)69     public boolean isSystemProviderEnabled(String path) {
70         return mSystemConditionProviderNames.contains(path);
71     }
72 
addSystemProvider(SystemConditionProviderService service)73     public void addSystemProvider(SystemConditionProviderService service) {
74         mSystemConditionProviders.add(service);
75         service.attachBase(mContext);
76         registerService(service.asInterface(), service.getComponent(), UserHandle.USER_SYSTEM);
77     }
78 
getSystemProviders()79     public Iterable<SystemConditionProviderService> getSystemProviders() {
80         return mSystemConditionProviders;
81     }
82 
83     @Override
getConfig()84     protected Config getConfig() {
85         final Config c = new Config();
86         c.caption = "condition provider";
87         c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
88         c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES;
89         c.xmlTag = TAG_ENABLED_DND_APPS;
90         c.secondarySettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
91         c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
92         c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS;
93         c.clientLabel = R.string.condition_provider_service_binding_label;
94         return c;
95     }
96 
97     @Override
dump(PrintWriter pw, DumpFilter filter)98     public void dump(PrintWriter pw, DumpFilter filter) {
99         super.dump(pw, filter);
100         synchronized(mMutex) {
101             pw.print("    mRecords("); pw.print(mRecords.size()); pw.println("):");
102             for (int i = 0; i < mRecords.size(); i++) {
103                 final ConditionRecord r = mRecords.get(i);
104                 if (filter != null && !filter.matches(r.component)) continue;
105                 pw.print("      "); pw.println(r);
106                 final String countdownDesc =  CountdownConditionProvider.tryParseDescription(r.id);
107                 if (countdownDesc != null) {
108                     pw.print("        ("); pw.print(countdownDesc); pw.println(")");
109                 }
110             }
111         }
112         pw.print("    mSystemConditionProviders: "); pw.println(mSystemConditionProviderNames);
113         for (int i = 0; i < mSystemConditionProviders.size(); i++) {
114             mSystemConditionProviders.valueAt(i).dump(pw, filter);
115         }
116     }
117 
118     @Override
asInterface(IBinder binder)119     protected IInterface asInterface(IBinder binder) {
120         return IConditionProvider.Stub.asInterface(binder);
121     }
122 
123     @Override
checkType(IInterface service)124     protected boolean checkType(IInterface service) {
125         return service instanceof IConditionProvider;
126     }
127 
128     @Override
onBootPhaseAppsCanStart()129     public void onBootPhaseAppsCanStart() {
130         super.onBootPhaseAppsCanStart();
131         for (int i = 0; i < mSystemConditionProviders.size(); i++) {
132             mSystemConditionProviders.valueAt(i).onBootComplete();
133         }
134         if (mCallback != null) {
135             mCallback.onBootComplete();
136         }
137     }
138 
139     @Override
onUserSwitched(int user)140     public void onUserSwitched(int user) {
141         super.onUserSwitched(user);
142         if (mCallback != null) {
143             mCallback.onUserSwitched();
144         }
145     }
146 
147     @Override
onServiceAdded(ManagedServiceInfo info)148     protected void onServiceAdded(ManagedServiceInfo info) {
149         final IConditionProvider provider = provider(info);
150         try {
151             provider.onConnected();
152         } catch (RemoteException e) {
153             // we tried
154         }
155         if (mCallback != null) {
156             mCallback.onServiceAdded(info.component);
157         }
158     }
159 
160     @Override
onServiceRemovedLocked(ManagedServiceInfo removed)161     protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
162         if (removed == null) return;
163         for (int i = mRecords.size() - 1; i >= 0; i--) {
164             final ConditionRecord r = mRecords.get(i);
165             if (!r.component.equals(removed.component)) continue;
166             mRecords.remove(i);
167         }
168     }
169 
170     @Override
onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uid)171     public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uid) {
172         if (removingPackage) {
173             INotificationManager inm = NotificationManager.getService();
174 
175             if (pkgList != null && (pkgList.length > 0)) {
176                 for (String pkgName : pkgList) {
177                     try {
178                         inm.removeAutomaticZenRules(pkgName);
179                         inm.setNotificationPolicyAccessGranted(pkgName, false);
180                     } catch (Exception e) {
181                         Slog.e(TAG, "Failed to clean up rules for " + pkgName, e);
182                     }
183                 }
184             }
185         }
186         super.onPackagesChanged(removingPackage, pkgList, uid);
187     }
188 
189     @Override
isValidEntry(String packageOrComponent, int userId)190     protected boolean isValidEntry(String packageOrComponent, int userId) {
191         return true;
192     }
193 
checkServiceToken(IConditionProvider provider)194     public ManagedServiceInfo checkServiceToken(IConditionProvider provider) {
195         synchronized(mMutex) {
196             return checkServiceTokenLocked(provider);
197         }
198     }
199 
removeDuplicateConditions(String pkg, Condition[] conditions)200     private Condition[] removeDuplicateConditions(String pkg, Condition[] conditions) {
201         if (conditions == null || conditions.length == 0) return null;
202         final int N = conditions.length;
203         final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N);
204         for (int i = 0; i < N; i++) {
205             final Uri id = conditions[i].id;
206             if (valid.containsKey(id)) {
207                 Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id);
208                 continue;
209             }
210             valid.put(id, conditions[i]);
211         }
212         if (valid.size() == 0) return null;
213         if (valid.size() == N) return conditions;
214         final Condition[] rt = new Condition[valid.size()];
215         for (int i = 0; i < rt.length; i++) {
216             rt[i] = valid.valueAt(i);
217         }
218         return rt;
219     }
220 
getRecordLocked(Uri id, ComponentName component, boolean create)221     private ConditionRecord getRecordLocked(Uri id, ComponentName component, boolean create) {
222         if (id == null || component == null) return null;
223         final int N = mRecords.size();
224         for (int i = 0; i < N; i++) {
225             final ConditionRecord r = mRecords.get(i);
226             if (r.id.equals(id) && r.component.equals(component)) {
227                 return r;
228             }
229         }
230         if (create) {
231             final ConditionRecord r = new ConditionRecord(id, component);
232             mRecords.add(r);
233             return r;
234         }
235         return null;
236     }
237 
notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions)238     public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
239         synchronized(mMutex) {
240             if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
241                     + (conditions == null ? null : Arrays.asList(conditions)));
242             conditions = removeDuplicateConditions(pkg, conditions);
243             if (conditions == null || conditions.length == 0) return;
244             final int N = conditions.length;
245             for (int i = 0; i < N; i++) {
246                 final Condition c = conditions[i];
247                 final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/);
248                 r.info = info;
249                 r.condition = c;
250             }
251         }
252         final int N = conditions.length;
253         for (int i = 0; i < N; i++) {
254             final Condition c = conditions[i];
255             if (mCallback != null) {
256                 mCallback.onConditionChanged(c.id, c);
257             }
258         }
259     }
260 
findConditionProvider(ComponentName component)261     public IConditionProvider findConditionProvider(ComponentName component) {
262         if (component == null) return null;
263         for (ManagedServiceInfo service : getServices()) {
264             if (component.equals(service.component)) {
265                 return provider(service);
266             }
267         }
268         return null;
269     }
270 
findCondition(ComponentName component, Uri conditionId)271     public Condition findCondition(ComponentName component, Uri conditionId) {
272         if (component == null || conditionId == null) return null;
273         synchronized (mMutex) {
274             final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
275             return r != null ? r.condition : null;
276         }
277     }
278 
ensureRecordExists(ComponentName component, Uri conditionId, IConditionProvider provider)279     public void ensureRecordExists(ComponentName component, Uri conditionId,
280             IConditionProvider provider) {
281         // constructed by convention, make sure the record exists...
282         final ConditionRecord r = getRecordLocked(conditionId, component, true /*create*/);
283         if (r.info == null) {
284             // ... and is associated with the in-process service
285             r.info = checkServiceTokenLocked(provider);
286         }
287     }
288 
subscribeIfNecessary(ComponentName component, Uri conditionId)289     public boolean subscribeIfNecessary(ComponentName component, Uri conditionId) {
290         synchronized (mMutex) {
291             final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
292             if (r == null) {
293                 Slog.w(TAG, "Unable to subscribe to " + component + " " + conditionId);
294                 return false;
295             }
296             if (r.subscribed) return true;
297             subscribeLocked(r);
298             return r.subscribed;
299         }
300     }
301 
unsubscribeIfNecessary(ComponentName component, Uri conditionId)302     public void unsubscribeIfNecessary(ComponentName component, Uri conditionId) {
303         synchronized (mMutex) {
304             final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
305             if (r == null) {
306                 Slog.w(TAG, "Unable to unsubscribe to " + component + " " + conditionId);
307                 return;
308             }
309             if (!r.subscribed) return;
310             unsubscribeLocked(r);;
311         }
312     }
313 
subscribeLocked(ConditionRecord r)314     private void subscribeLocked(ConditionRecord r) {
315         if (DEBUG) Slog.d(TAG, "subscribeLocked " + r);
316         final IConditionProvider provider = provider(r);
317         RemoteException re = null;
318         if (provider != null) {
319             try {
320                 Slog.d(TAG, "Subscribing to " + r.id + " with " + r.component);
321                 provider.onSubscribe(r.id);
322                 r.subscribed = true;
323             } catch (RemoteException e) {
324                 Slog.w(TAG, "Error subscribing to " + r, e);
325                 re = e;
326             }
327         }
328         ZenLog.traceSubscribe(r != null ? r.id : null, provider, re);
329     }
330 
331     @SafeVarargs
safeSet(T... items)332     private static <T> ArraySet<T> safeSet(T... items) {
333         final ArraySet<T> rt = new ArraySet<T>();
334         if (items == null || items.length == 0) return rt;
335         final int N = items.length;
336         for (int i = 0; i < N; i++) {
337             final T item = items[i];
338             if (item != null) {
339                 rt.add(item);
340             }
341         }
342         return rt;
343     }
344 
unsubscribeLocked(ConditionRecord r)345     private void unsubscribeLocked(ConditionRecord r) {
346         if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
347         final IConditionProvider provider = provider(r);
348         RemoteException re = null;
349         if (provider != null) {
350             try {
351                 provider.onUnsubscribe(r.id);
352             } catch (RemoteException e) {
353                 Slog.w(TAG, "Error unsubscribing to " + r, e);
354                 re = e;
355             }
356             r.subscribed = false;
357         }
358         ZenLog.traceUnsubscribe(r != null ? r.id : null, provider, re);
359     }
360 
provider(ConditionRecord r)361     private static IConditionProvider provider(ConditionRecord r) {
362         return r == null ? null : provider(r.info);
363     }
364 
provider(ManagedServiceInfo info)365     private static IConditionProvider provider(ManagedServiceInfo info) {
366         return info == null ? null : (IConditionProvider) info.service;
367     }
368 
369     private static class ConditionRecord {
370         public final Uri id;
371         public final ComponentName component;
372         public Condition condition;
373         public ManagedServiceInfo info;
374         public boolean subscribed;
375 
ConditionRecord(Uri id, ComponentName component)376         private ConditionRecord(Uri id, ComponentName component) {
377             this.id = id;
378             this.component = component;
379         }
380 
381         @Override
toString()382         public String toString() {
383             final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
384                     .append(id).append(",component=").append(component)
385                     .append(",subscribed=").append(subscribed);
386             return sb.append(']').toString();
387         }
388     }
389 
390     public interface Callback {
onBootComplete()391         void onBootComplete();
onServiceAdded(ComponentName component)392         void onServiceAdded(ComponentName component);
onConditionChanged(Uri id, Condition condition)393         void onConditionChanged(Uri id, Condition condition);
onUserSwitched()394         void onUserSwitched();
395     }
396 
397 }
398