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