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