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