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