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