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             Slog.e(TAG, "can't connect to service " + info, e);
154             // we tried
155         }
156         if (mCallback != null) {
157             mCallback.onServiceAdded(info.component);
158         }
159     }
160 
161     @Override
onServiceRemovedLocked(ManagedServiceInfo removed)162     protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
163         if (removed == null) return;
164         for (int i = mRecords.size() - 1; i >= 0; i--) {
165             final ConditionRecord r = mRecords.get(i);
166             if (!r.component.equals(removed.component)) continue;
167             mRecords.remove(i);
168         }
169     }
170 
171     @Override
onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uid)172     public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uid) {
173         if (removingPackage) {
174             INotificationManager inm = NotificationManager.getService();
175 
176             if (pkgList != null && (pkgList.length > 0)) {
177                 for (String pkgName : pkgList) {
178                     try {
179                         inm.removeAutomaticZenRules(pkgName);
180                         inm.setNotificationPolicyAccessGranted(pkgName, false);
181                     } catch (Exception e) {
182                         Slog.e(TAG, "Failed to clean up rules for " + pkgName, e);
183                     }
184                 }
185             }
186         }
187         super.onPackagesChanged(removingPackage, pkgList, uid);
188     }
189 
190     @Override
isValidEntry(String packageOrComponent, int userId)191     protected boolean isValidEntry(String packageOrComponent, int userId) {
192         return true;
193     }
194 
195     @Override
getRequiredPermission()196     protected String getRequiredPermission() {
197         return null;
198     }
199 
checkServiceToken(IConditionProvider provider)200     public ManagedServiceInfo checkServiceToken(IConditionProvider provider) {
201         synchronized(mMutex) {
202             return checkServiceTokenLocked(provider);
203         }
204     }
205 
removeDuplicateConditions(String pkg, Condition[] conditions)206     private Condition[] removeDuplicateConditions(String pkg, Condition[] conditions) {
207         if (conditions == null || conditions.length == 0) return null;
208         final int N = conditions.length;
209         final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N);
210         for (int i = 0; i < N; i++) {
211             final Uri id = conditions[i].id;
212             if (valid.containsKey(id)) {
213                 Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id);
214                 continue;
215             }
216             valid.put(id, conditions[i]);
217         }
218         if (valid.size() == 0) return null;
219         if (valid.size() == N) return conditions;
220         final Condition[] rt = new Condition[valid.size()];
221         for (int i = 0; i < rt.length; i++) {
222             rt[i] = valid.valueAt(i);
223         }
224         return rt;
225     }
226 
getRecordLocked(Uri id, ComponentName component, boolean create)227     private ConditionRecord getRecordLocked(Uri id, ComponentName component, boolean create) {
228         if (id == null || component == null) return null;
229         final int N = mRecords.size();
230         for (int i = 0; i < N; i++) {
231             final ConditionRecord r = mRecords.get(i);
232             if (r.id.equals(id) && r.component.equals(component)) {
233                 return r;
234             }
235         }
236         if (create) {
237             final ConditionRecord r = new ConditionRecord(id, component);
238             mRecords.add(r);
239             return r;
240         }
241         return null;
242     }
243 
notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions)244     public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
245         synchronized(mMutex) {
246             if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
247                     + (conditions == null ? null : Arrays.asList(conditions)));
248             conditions = removeDuplicateConditions(pkg, conditions);
249             if (conditions == null || conditions.length == 0) return;
250             final int N = conditions.length;
251             for (int i = 0; i < N; i++) {
252                 final Condition c = conditions[i];
253                 final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/);
254                 r.info = info;
255                 r.condition = c;
256             }
257         }
258         final int N = conditions.length;
259         for (int i = 0; i < N; i++) {
260             final Condition c = conditions[i];
261             if (mCallback != null) {
262                 mCallback.onConditionChanged(c.id, c);
263             }
264         }
265     }
266 
findConditionProvider(ComponentName component)267     public IConditionProvider findConditionProvider(ComponentName component) {
268         if (component == null) return null;
269         for (ManagedServiceInfo service : getServices()) {
270             if (component.equals(service.component)) {
271                 return provider(service);
272             }
273         }
274         return null;
275     }
276 
findCondition(ComponentName component, Uri conditionId)277     public Condition findCondition(ComponentName component, Uri conditionId) {
278         if (component == null || conditionId == null) return null;
279         synchronized (mMutex) {
280             final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
281             return r != null ? r.condition : null;
282         }
283     }
284 
ensureRecordExists(ComponentName component, Uri conditionId, IConditionProvider provider)285     public void ensureRecordExists(ComponentName component, Uri conditionId,
286             IConditionProvider provider) {
287         synchronized (mMutex) {
288             // constructed by convention, make sure the record exists...
289             final ConditionRecord r = getRecordLocked(conditionId, component, true /*create*/);
290             if (r.info == null) {
291                 // ... and is associated with the in-process service
292                 r.info = checkServiceTokenLocked(provider);
293             }
294         }
295     }
296 
subscribeIfNecessary(ComponentName component, Uri conditionId)297     public boolean subscribeIfNecessary(ComponentName component, Uri conditionId) {
298         synchronized (mMutex) {
299             final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
300             if (r == null) {
301                 Slog.w(TAG, "Unable to subscribe to " + component + " " + conditionId);
302                 return false;
303             }
304             if (r.subscribed) return true;
305             subscribeLocked(r);
306             return r.subscribed;
307         }
308     }
309 
unsubscribeIfNecessary(ComponentName component, Uri conditionId)310     public void unsubscribeIfNecessary(ComponentName component, Uri conditionId) {
311         synchronized (mMutex) {
312             final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
313             if (r == null) {
314                 Slog.w(TAG, "Unable to unsubscribe to " + component + " " + conditionId);
315                 return;
316             }
317             if (!r.subscribed) return;
318             unsubscribeLocked(r);;
319         }
320     }
321 
subscribeLocked(ConditionRecord r)322     private void subscribeLocked(ConditionRecord r) {
323         if (DEBUG) Slog.d(TAG, "subscribeLocked " + r);
324         final IConditionProvider provider = provider(r);
325         RemoteException re = null;
326         if (provider != null) {
327             try {
328                 Slog.d(TAG, "Subscribing to " + r.id + " with " + r.component);
329                 provider.onSubscribe(r.id);
330                 r.subscribed = true;
331             } catch (RemoteException e) {
332                 Slog.w(TAG, "Error subscribing to " + r, e);
333                 re = e;
334             }
335         }
336         ZenLog.traceSubscribe(r != null ? r.id : null, provider, re);
337     }
338 
339     @SafeVarargs
safeSet(T... items)340     private static <T> ArraySet<T> safeSet(T... items) {
341         final ArraySet<T> rt = new ArraySet<T>();
342         if (items == null || items.length == 0) return rt;
343         final int N = items.length;
344         for (int i = 0; i < N; i++) {
345             final T item = items[i];
346             if (item != null) {
347                 rt.add(item);
348             }
349         }
350         return rt;
351     }
352 
unsubscribeLocked(ConditionRecord r)353     private void unsubscribeLocked(ConditionRecord r) {
354         if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
355         final IConditionProvider provider = provider(r);
356         RemoteException re = null;
357         if (provider != null) {
358             try {
359                 provider.onUnsubscribe(r.id);
360             } catch (RemoteException e) {
361                 Slog.w(TAG, "Error unsubscribing to " + r, e);
362                 re = e;
363             }
364             r.subscribed = false;
365         }
366         ZenLog.traceUnsubscribe(r != null ? r.id : null, provider, re);
367     }
368 
provider(ConditionRecord r)369     private static IConditionProvider provider(ConditionRecord r) {
370         return r == null ? null : provider(r.info);
371     }
372 
provider(ManagedServiceInfo info)373     private static IConditionProvider provider(ManagedServiceInfo info) {
374         return info == null ? null : (IConditionProvider) info.service;
375     }
376 
377     private static class ConditionRecord {
378         public final Uri id;
379         public final ComponentName component;
380         public Condition condition;
381         public ManagedServiceInfo info;
382         public boolean subscribed;
383 
ConditionRecord(Uri id, ComponentName component)384         private ConditionRecord(Uri id, ComponentName component) {
385             this.id = id;
386             this.component = component;
387         }
388 
389         @Override
toString()390         public String toString() {
391             final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
392                     .append(id).append(",component=").append(component)
393                     .append(",subscribed=").append(subscribed);
394             return sb.append(']').toString();
395         }
396     }
397 
398     public interface Callback {
onBootComplete()399         void onBootComplete();
onServiceAdded(ComponentName component)400         void onServiceAdded(ComponentName component);
onConditionChanged(Uri id, Condition condition)401         void onConditionChanged(Uri id, Condition condition);
onUserSwitched()402         void onUserSwitched();
403     }
404 
405 }
406