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