ConditionProviders.java revision 25e2d24f24040e58a61169db797f760b16c11f5d
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.content.ComponentName;
20import android.content.Context;
21import android.net.Uri;
22import android.os.Handler;
23import android.os.IBinder;
24import android.os.IInterface;
25import android.os.RemoteException;
26import android.os.UserHandle;
27import android.provider.Settings;
28import android.provider.Settings.Global;
29import android.service.notification.Condition;
30import android.service.notification.ConditionProviderService;
31import android.service.notification.IConditionListener;
32import android.service.notification.IConditionProvider;
33import android.service.notification.ZenModeConfig;
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;
44import java.util.Objects;
45
46public class ConditionProviders extends ManagedServices {
47    private static final Condition[] NO_CONDITIONS = new Condition[0];
48
49    private final ZenModeHelper mZenModeHelper;
50    private final ArrayMap<IBinder, IConditionListener> mListeners
51            = new ArrayMap<IBinder, IConditionListener>();
52    private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>();
53    private final CountdownConditionProvider mCountdown = new CountdownConditionProvider();
54
55    private Uri mExitConditionId;
56
57    public ConditionProviders(Context context, Handler handler,
58            UserProfiles userProfiles, ZenModeHelper zenModeHelper) {
59        super(context, handler, new Object(), userProfiles);
60        mZenModeHelper = zenModeHelper;
61        mZenModeHelper.addCallback(new ZenModeHelperCallback());
62        loadZenConfig();
63    }
64
65    @Override
66    protected Config getConfig() {
67        Config c = new Config();
68        c.caption = "condition provider";
69        c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
70        c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
71        c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
72        c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS;
73        c.clientLabel = R.string.condition_provider_service_binding_label;
74        return c;
75    }
76
77    @Override
78    public void dump(PrintWriter pw, DumpFilter filter) {
79        super.dump(pw, filter);
80        synchronized(mMutex) {
81            if (filter == null) {
82                pw.print("    mListeners("); pw.print(mListeners.size()); pw.println("):");
83                for (int i = 0; i < mListeners.size(); i++) {
84                    pw.print("      "); pw.println(mListeners.keyAt(i));
85                }
86            }
87            pw.print("    mRecords("); pw.print(mRecords.size()); pw.println("):");
88            for (int i = 0; i < mRecords.size(); i++) {
89                final ConditionRecord r = mRecords.get(i);
90                if (filter != null && !filter.matches(r.component)) continue;
91                pw.print("      "); pw.println(r);
92                final String countdownDesc =  CountdownConditionProvider.tryParseDescription(r.id);
93                if (countdownDesc != null) {
94                    pw.print("        ("); pw.print(countdownDesc); pw.println(")");
95                }
96            }
97        }
98    }
99
100    @Override
101    protected IInterface asInterface(IBinder binder) {
102        return IConditionProvider.Stub.asInterface(binder);
103    }
104
105    @Override
106    public void onBootPhaseAppsCanStart() {
107        super.onBootPhaseAppsCanStart();
108        mCountdown.attachBase(mContext);
109        registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT,
110                UserHandle.USER_OWNER);
111    }
112
113    @Override
114    protected void onServiceAdded(ManagedServiceInfo info) {
115        Slog.d(TAG, "onServiceAdded " + info);
116        final IConditionProvider provider = provider(info);
117        try {
118            provider.onConnected();
119        } catch (RemoteException e) {
120            // we tried
121        }
122        synchronized (mMutex) {
123            final int N = mRecords.size();
124            for(int i = 0; i < N; i++) {
125                final ConditionRecord r = mRecords.get(i);
126                if (!r.component.equals(info.component)) continue;
127                r.info = info;
128                // if automatic, auto-subscribe
129                if (r.isAutomatic) {
130                    try {
131                        final Uri id = r.id;
132                        if (DEBUG) Slog.d(TAG, "Auto-subscribing to configured condition " + id);
133                        provider.onSubscribe(id);
134                    } catch (RemoteException e) {
135                        // we tried
136                    }
137                }
138            }
139        }
140    }
141
142    @Override
143    protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
144        if (removed == null) return;
145        for (int i = mRecords.size() - 1; i >= 0; i--) {
146            final ConditionRecord r = mRecords.get(i);
147            if (!r.component.equals(removed.component)) continue;
148            if (r.isManual) {
149                // removing the current manual condition, exit zen
150                mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF);
151            }
152            if (r.isAutomatic) {
153                // removing an automatic condition, exit zen
154                mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF);
155            }
156            mRecords.remove(i);
157        }
158    }
159
160    public ManagedServiceInfo checkServiceToken(IConditionProvider provider) {
161        synchronized(mMutex) {
162            return checkServiceTokenLocked(provider);
163        }
164    }
165
166    public void requestZenModeConditions(IConditionListener callback, int relevance) {
167        synchronized(mMutex) {
168            if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback
169                    + " relevance=" + Condition.relevanceToString(relevance));
170            if (callback == null) return;
171            relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS);
172            if (relevance != 0) {
173                mListeners.put(callback.asBinder(), callback);
174                requestConditionsLocked(relevance);
175            } else {
176                mListeners.remove(callback.asBinder());
177                if (mListeners.isEmpty()) {
178                    requestConditionsLocked(0);
179                }
180            }
181        }
182    }
183
184    private Condition[] validateConditions(String pkg, Condition[] conditions) {
185        if (conditions == null || conditions.length == 0) return null;
186        final int N = conditions.length;
187        final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N);
188        for (int i = 0; i < N; i++) {
189            final Uri id = conditions[i].id;
190            if (!Condition.isValidId(id, pkg)) {
191                Slog.w(TAG, "Ignoring condition from " + pkg + " for invalid id: " + id);
192                continue;
193            }
194            if (valid.containsKey(id)) {
195                Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id);
196                continue;
197            }
198            valid.put(id, conditions[i]);
199        }
200        if (valid.size() == 0) return null;
201        if (valid.size() == N) return conditions;
202        final Condition[] rt = new Condition[valid.size()];
203        for (int i = 0; i < rt.length; i++) {
204            rt[i] = valid.valueAt(i);
205        }
206        return rt;
207    }
208
209    private ConditionRecord getRecordLocked(Uri id, ComponentName component) {
210        final int N = mRecords.size();
211        for (int i = 0; i < N; i++) {
212            final ConditionRecord r = mRecords.get(i);
213            if (r.id.equals(id) && r.component.equals(component)) {
214                return r;
215            }
216        }
217        final ConditionRecord r = new ConditionRecord(id, component);
218        mRecords.add(r);
219        return r;
220    }
221
222    public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
223        synchronized(mMutex) {
224            if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
225                    + (conditions == null ? null : Arrays.asList(conditions)));
226            conditions = validateConditions(pkg, conditions);
227            if (conditions == null || conditions.length == 0) return;
228            final int N = conditions.length;
229            for (IConditionListener listener : mListeners.values()) {
230                try {
231                    listener.onConditionsReceived(conditions);
232                } catch (RemoteException e) {
233                    Slog.w(TAG, "Error sending conditions to listener " + listener, e);
234                }
235            }
236            for (int i = 0; i < N; i++) {
237                final Condition c = conditions[i];
238                final ConditionRecord r = getRecordLocked(c.id, info.component);
239                r.info = info;
240                r.condition = c;
241                // if manual, exit zen if false (or failed)
242                if (r.isManual) {
243                    if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
244                        final boolean failed = c.state == Condition.STATE_ERROR;
245                        if (failed) {
246                            Slog.w(TAG, "Exit zen: manual condition failed: " + c);
247                        } else if (DEBUG) {
248                            Slog.d(TAG, "Exit zen: manual condition false: " + c);
249                        }
250                        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF);
251                        unsubscribeLocked(r);
252                        r.isManual = false;
253                    }
254                }
255                // if automatic, exit zen if false (or failed), enter zen if true
256                if (r.isAutomatic) {
257                    if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
258                        final boolean failed = c.state == Condition.STATE_ERROR;
259                        if (failed) {
260                            Slog.w(TAG, "Exit zen: automatic condition failed: " + c);
261                        } else if (DEBUG) {
262                            Slog.d(TAG, "Exit zen: automatic condition false: " + c);
263                        }
264                        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF);
265                    } else if (c.state == Condition.STATE_TRUE) {
266                        Slog.d(TAG, "Enter zen: automatic condition true: " + c);
267                        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_ON);
268                    }
269                }
270            }
271        }
272    }
273
274    public void setZenModeCondition(Uri conditionId) {
275        if (DEBUG) Slog.d(TAG, "setZenModeCondition " + conditionId);
276        synchronized(mMutex) {
277            if (ZenModeConfig.isValidCountdownConditionId(conditionId)) {
278                // constructed by the client, make sure the record exists...
279                final ConditionRecord r = getRecordLocked(conditionId,
280                        CountdownConditionProvider.COMPONENT);
281                if (r.info == null) {
282                    // ... and is associated with the in-process service
283                    r.info = checkServiceTokenLocked(mCountdown.asInterface());
284                }
285            }
286            final int N = mRecords.size();
287            for (int i = 0; i < N; i++) {
288                final ConditionRecord r = mRecords.get(i);
289                final boolean idEqual = r.id.equals(conditionId);
290                if (r.isManual && !idEqual) {
291                    // was previous manual condition, unsubscribe
292                    unsubscribeLocked(r);
293                    r.isManual = false;
294                } else if (idEqual && !r.isManual) {
295                    // is new manual condition, subscribe
296                    subscribeLocked(r);
297                    r.isManual = true;
298                }
299            }
300            if (!Objects.equals(mExitConditionId, conditionId)) {
301                mExitConditionId = conditionId;
302                saveZenConfigLocked();
303            }
304        }
305    }
306
307    private void subscribeLocked(ConditionRecord r) {
308        if (DEBUG) Slog.d(TAG, "subscribeLocked " + r);
309        final IConditionProvider provider = provider(r);
310        if (provider == null) {
311            Slog.w(TAG, "subscribeLocked: no provider");
312            return;
313        }
314        try {
315            provider.onSubscribe(r.id);
316        } catch (RemoteException e) {
317            Slog.w(TAG, "Error subscribing to " + r, e);
318        }
319    }
320
321    private static <T> ArraySet<T> safeSet(T... items) {
322        final ArraySet<T> rt = new ArraySet<T>();
323        if (items == null || items.length == 0) return rt;
324        final int N = items.length;
325        for (int i = 0; i < N; i++) {
326            final T item = items[i];
327            if (item != null) {
328                rt.add(item);
329            }
330        }
331        return rt;
332    }
333
334    public void setAutomaticZenModeConditions(Uri[] conditionIds) {
335        setAutomaticZenModeConditions(conditionIds, true /*save*/);
336    }
337
338    private void setAutomaticZenModeConditions(Uri[] conditionIds, boolean save) {
339        if (DEBUG) Slog.d(TAG, "setAutomaticZenModeConditions "
340                + (conditionIds == null ? null : Arrays.asList(conditionIds)));
341        synchronized(mMutex) {
342            final ArraySet<Uri> newIds = safeSet(conditionIds);
343            final int N = mRecords.size();
344            boolean changed = false;
345            for (int i = 0; i < N; i++) {
346                final ConditionRecord r = mRecords.get(i);
347                final boolean automatic = newIds.contains(r.id);
348                if (!r.isAutomatic && automatic) {
349                    // subscribe to new automatic
350                    subscribeLocked(r);
351                    r.isAutomatic = true;
352                    changed = true;
353                } else if (r.isAutomatic && !automatic) {
354                    // unsubscribe from old automatic
355                    unsubscribeLocked(r);
356                    r.isAutomatic = false;
357                    changed = true;
358                }
359            }
360            if (save && changed) {
361                saveZenConfigLocked();
362            }
363        }
364    }
365
366    public Condition[] getAutomaticZenModeConditions() {
367        synchronized(mMutex) {
368            final int N = mRecords.size();
369            ArrayList<Condition> rt = null;
370            for (int i = 0; i < N; i++) {
371                final ConditionRecord r = mRecords.get(i);
372                if (r.isAutomatic && r.condition != null) {
373                    if (rt == null) rt = new ArrayList<Condition>();
374                    rt.add(r.condition);
375                }
376            }
377            return rt == null ? NO_CONDITIONS : rt.toArray(new Condition[rt.size()]);
378        }
379    }
380
381    private void unsubscribeLocked(ConditionRecord r) {
382        if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
383        final IConditionProvider provider = provider(r);
384        if (provider == null) {
385            Slog.w(TAG, "unsubscribeLocked: no provider");
386            return;
387        }
388        try {
389            provider.onUnsubscribe(r.id);
390        } catch (RemoteException e) {
391            Slog.w(TAG, "Error unsubscribing to " + r, e);
392        }
393    }
394
395    private static IConditionProvider provider(ConditionRecord r) {
396        return r == null ? null : provider(r.info);
397    }
398
399    private static IConditionProvider provider(ManagedServiceInfo info) {
400        return info == null ? null : (IConditionProvider) info.service;
401    }
402
403    private void requestConditionsLocked(int flags) {
404        for (ManagedServiceInfo info : mServices) {
405            final IConditionProvider provider = provider(info);
406            if (provider == null) continue;
407            try {
408                provider.onRequestConditions(flags);
409            } catch (RemoteException e) {
410                Slog.w(TAG, "Error requesting conditions from " + info.component, e);
411            }
412        }
413    }
414
415    private void loadZenConfig() {
416        final ZenModeConfig config = mZenModeHelper.getConfig();
417        if (config == null) {
418            if (DEBUG) Slog.d(TAG, "loadZenConfig: no config");
419            return;
420        }
421        synchronized (mMutex) {
422            mExitConditionId = config.exitConditionId;
423            if (config.conditionComponents == null || config.conditionIds == null
424                    || config.conditionComponents.length != config.conditionIds.length) {
425                if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions");
426                setAutomaticZenModeConditions(null, false /*save*/);
427                return;
428            }
429            final ArraySet<Uri> newIds = new ArraySet<Uri>();
430            final int N = config.conditionComponents.length;
431            for (int i = 0; i < N; i++) {
432                final ComponentName component = config.conditionComponents[i];
433                final Uri id = config.conditionIds[i];
434                if (component != null && id != null) {
435                    getRecordLocked(id, component);  // ensure record exists
436                    newIds.add(id);
437                }
438            }
439            if (DEBUG) Slog.d(TAG, "loadZenConfig: N=" + N);
440            setAutomaticZenModeConditions(newIds.toArray(new Uri[newIds.size()]), false /*save*/);
441        }
442    }
443
444    private void saveZenConfigLocked() {
445        ZenModeConfig config = mZenModeHelper.getConfig();
446        if (config == null) return;
447        config = config.copy();
448        final ArrayList<ConditionRecord> automatic = new ArrayList<ConditionRecord>();
449        final int automaticN = mRecords.size();
450        for (int i = 0; i < automaticN; i++) {
451            final ConditionRecord r = mRecords.get(i);
452            if (r.isAutomatic) {
453                automatic.add(r);
454            }
455        }
456        if (automatic.isEmpty()) {
457            config.conditionComponents = null;
458            config.conditionIds = null;
459        } else {
460            final int N = automatic.size();
461            config.conditionComponents = new ComponentName[N];
462            config.conditionIds = new Uri[N];
463            for (int i = 0; i < N; i++) {
464                final ConditionRecord r = automatic.get(i);
465                config.conditionComponents[i] = r.component;
466                config.conditionIds[i] = r.id;
467            }
468        }
469        config.exitConditionId = mExitConditionId;
470        if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config);
471        mZenModeHelper.setConfig(config);
472    }
473
474    private class ZenModeHelperCallback extends ZenModeHelper.Callback {
475        @Override
476        void onConfigChanged() {
477            loadZenConfig();
478        }
479
480        @Override
481        void onZenModeChanged() {
482            final int mode = mZenModeHelper.getZenMode();
483            if (mode == Global.ZEN_MODE_OFF) {
484                // ensure any manual condition is cleared
485                setZenModeCondition(null);
486            }
487        }
488    }
489
490    private static class ConditionRecord {
491        public final Uri id;
492        public final ComponentName component;
493        public Condition condition;
494        public ManagedServiceInfo info;
495        public boolean isAutomatic;
496        public boolean isManual;
497
498        private ConditionRecord(Uri id, ComponentName component) {
499            this.id = id;
500            this.component = component;
501        }
502
503        @Override
504        public String toString() {
505            final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
506                    .append(id).append(",component=").append(component);
507            if (isAutomatic) sb.append(",automatic");
508            if (isManual) sb.append(",manual");
509            return sb.append(']').toString();
510        }
511    }
512}
513