ConditionProviders.java revision 6b0623a1129a7e1bc4949b7dca4fa133ff322c4a
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                r.info = info;
264                r.condition = c;
265                // if manual, exit zen if false (or failed)
266                if (r.isManual) {
267                    if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
268                        final boolean failed = c.state == Condition.STATE_ERROR;
269                        if (failed) {
270                            Slog.w(TAG, "Exit zen: manual condition failed: " + c);
271                        } else if (DEBUG) {
272                            Slog.d(TAG, "Exit zen: manual condition false: " + c);
273                        }
274                        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
275                                "manualConditionExit");
276                        unsubscribeLocked(r);
277                        r.isManual = false;
278                    }
279                }
280                // if automatic, exit zen if false (or failed), enter zen if true
281                if (r.isAutomatic) {
282                    if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
283                        final boolean failed = c.state == Condition.STATE_ERROR;
284                        if (failed) {
285                            Slog.w(TAG, "Exit zen: automatic condition failed: " + c);
286                        } else if (DEBUG) {
287                            Slog.d(TAG, "Exit zen: automatic condition false: " + c);
288                        }
289                        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
290                                "automaticConditionExit");
291                    } else if (c.state == Condition.STATE_TRUE) {
292                        Slog.d(TAG, "Enter zen: automatic condition true: " + c);
293                        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
294                                "automaticConditionEnter");
295                    }
296                }
297            }
298        }
299    }
300
301    public void setZenModeCondition(Condition condition, String reason) {
302        if (DEBUG) Slog.d(TAG, "setZenModeCondition " + condition);
303        synchronized(mMutex) {
304            ComponentName conditionComponent = null;
305            if (condition != null) {
306                if (ZenModeConfig.isValidCountdownConditionId(condition.id)) {
307                    // constructed by the client, make sure the record exists...
308                    final ConditionRecord r = getRecordLocked(condition.id,
309                            CountdownConditionProvider.COMPONENT);
310                    if (r.info == null) {
311                        // ... and is associated with the in-process service
312                        r.info = checkServiceTokenLocked(mCountdown.asInterface());
313                    }
314                }
315                if (ZenModeConfig.isValidDowntimeConditionId(condition.id)) {
316                    // constructed by the client, make sure the record exists...
317                    final ConditionRecord r = getRecordLocked(condition.id,
318                            DowntimeConditionProvider.COMPONENT);
319                    if (r.info == null) {
320                        // ... and is associated with the in-process service
321                        r.info = checkServiceTokenLocked(mDowntime.asInterface());
322                    }
323                }
324            }
325            final int N = mRecords.size();
326            for (int i = 0; i < N; i++) {
327                final ConditionRecord r = mRecords.get(i);
328                final boolean idEqual = condition != null && r.id.equals(condition.id);
329                if (r.isManual && !idEqual) {
330                    // was previous manual condition, unsubscribe
331                    unsubscribeLocked(r);
332                    r.isManual = false;
333                } else if (idEqual && !r.isManual) {
334                    // is new manual condition, subscribe
335                    subscribeLocked(r);
336                    r.isManual = true;
337                }
338                if (idEqual) {
339                    conditionComponent = r.component;
340                }
341            }
342            if (!Objects.equals(mExitCondition, condition)) {
343                mExitCondition = condition;
344                mExitConditionComponent = conditionComponent;
345                ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, reason);
346                saveZenConfigLocked();
347            }
348        }
349    }
350
351    private void subscribeLocked(ConditionRecord r) {
352        if (DEBUG) Slog.d(TAG, "subscribeLocked " + r);
353        final IConditionProvider provider = provider(r);
354        RemoteException re = null;
355        if (provider != null) {
356            try {
357                Slog.d(TAG, "Subscribing to " + r.id + " with " + provider);
358                provider.onSubscribe(r.id);
359            } catch (RemoteException e) {
360                Slog.w(TAG, "Error subscribing to " + r, e);
361                re = e;
362            }
363        }
364        ZenLog.traceSubscribe(r != null ? r.id : null, provider, re);
365    }
366
367    private static <T> ArraySet<T> safeSet(T... items) {
368        final ArraySet<T> rt = new ArraySet<T>();
369        if (items == null || items.length == 0) return rt;
370        final int N = items.length;
371        for (int i = 0; i < N; i++) {
372            final T item = items[i];
373            if (item != null) {
374                rt.add(item);
375            }
376        }
377        return rt;
378    }
379
380    public void setAutomaticZenModeConditions(Uri[] conditionIds) {
381        setAutomaticZenModeConditions(conditionIds, true /*save*/);
382    }
383
384    private void setAutomaticZenModeConditions(Uri[] conditionIds, boolean save) {
385        if (DEBUG) Slog.d(TAG, "setAutomaticZenModeConditions "
386                + (conditionIds == null ? null : Arrays.asList(conditionIds)));
387        synchronized(mMutex) {
388            final ArraySet<Uri> newIds = safeSet(conditionIds);
389            final int N = mRecords.size();
390            boolean changed = false;
391            for (int i = 0; i < N; i++) {
392                final ConditionRecord r = mRecords.get(i);
393                final boolean automatic = newIds.contains(r.id);
394                if (!r.isAutomatic && automatic) {
395                    // subscribe to new automatic
396                    subscribeLocked(r);
397                    r.isAutomatic = true;
398                    changed = true;
399                } else if (r.isAutomatic && !automatic) {
400                    // unsubscribe from old automatic
401                    unsubscribeLocked(r);
402                    r.isAutomatic = false;
403                    changed = true;
404                }
405            }
406            if (save && changed) {
407                saveZenConfigLocked();
408            }
409        }
410    }
411
412    public Condition[] getAutomaticZenModeConditions() {
413        synchronized(mMutex) {
414            final int N = mRecords.size();
415            ArrayList<Condition> rt = null;
416            for (int i = 0; i < N; i++) {
417                final ConditionRecord r = mRecords.get(i);
418                if (r.isAutomatic && r.condition != null) {
419                    if (rt == null) rt = new ArrayList<Condition>();
420                    rt.add(r.condition);
421                }
422            }
423            return rt == null ? NO_CONDITIONS : rt.toArray(new Condition[rt.size()]);
424        }
425    }
426
427    private void unsubscribeLocked(ConditionRecord r) {
428        if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
429        final IConditionProvider provider = provider(r);
430        RemoteException re = null;
431        if (provider != null) {
432            try {
433                provider.onUnsubscribe(r.id);
434            } catch (RemoteException e) {
435                Slog.w(TAG, "Error unsubscribing to " + r, e);
436                re = e;
437            }
438        }
439        ZenLog.traceUnsubscribe(r != null ? r.id : null, provider, re);
440    }
441
442    private static IConditionProvider provider(ConditionRecord r) {
443        return r == null ? null : provider(r.info);
444    }
445
446    private static IConditionProvider provider(ManagedServiceInfo info) {
447        return info == null ? null : (IConditionProvider) info.service;
448    }
449
450    private void requestConditionsLocked(int flags) {
451        for (ManagedServiceInfo info : mServices) {
452            final IConditionProvider provider = provider(info);
453            if (provider == null) continue;
454            // clear all stored conditions from this provider that we no longer care about
455            for (int i = mRecords.size() - 1; i >= 0; i--) {
456                final ConditionRecord r = mRecords.get(i);
457                if (r.info != info) continue;
458                if (r.isManual || r.isAutomatic) continue;
459                mRecords.remove(i);
460            }
461            try {
462                provider.onRequestConditions(flags);
463            } catch (RemoteException e) {
464                Slog.w(TAG, "Error requesting conditions from " + info.component, e);
465            }
466        }
467    }
468
469    private void loadZenConfig() {
470        final ZenModeConfig config = mZenModeHelper.getConfig();
471        if (config == null) {
472            if (DEBUG) Slog.d(TAG, "loadZenConfig: no config");
473            return;
474        }
475        synchronized (mMutex) {
476            final boolean changingExit = !Objects.equals(mExitCondition, config.exitCondition);
477            mExitCondition = config.exitCondition;
478            mExitConditionComponent = config.exitConditionComponent;
479            if (changingExit) {
480                ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, "config");
481            }
482            mDowntime.setConfig(config);
483            if (config.conditionComponents == null || config.conditionIds == null
484                    || config.conditionComponents.length != config.conditionIds.length) {
485                if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions");
486                setAutomaticZenModeConditions(null, false /*save*/);
487                return;
488            }
489            final ArraySet<Uri> newIds = new ArraySet<Uri>();
490            final int N = config.conditionComponents.length;
491            for (int i = 0; i < N; i++) {
492                final ComponentName component = config.conditionComponents[i];
493                final Uri id = config.conditionIds[i];
494                if (component != null && id != null) {
495                    getRecordLocked(id, component);  // ensure record exists
496                    newIds.add(id);
497                }
498            }
499            if (DEBUG) Slog.d(TAG, "loadZenConfig: N=" + N);
500            setAutomaticZenModeConditions(newIds.toArray(new Uri[newIds.size()]), false /*save*/);
501        }
502    }
503
504    private void saveZenConfigLocked() {
505        ZenModeConfig config = mZenModeHelper.getConfig();
506        if (config == null) return;
507        config = config.copy();
508        final ArrayList<ConditionRecord> automatic = new ArrayList<ConditionRecord>();
509        final int automaticN = mRecords.size();
510        for (int i = 0; i < automaticN; i++) {
511            final ConditionRecord r = mRecords.get(i);
512            if (r.isAutomatic) {
513                automatic.add(r);
514            }
515        }
516        if (automatic.isEmpty()) {
517            config.conditionComponents = null;
518            config.conditionIds = null;
519        } else {
520            final int N = automatic.size();
521            config.conditionComponents = new ComponentName[N];
522            config.conditionIds = new Uri[N];
523            for (int i = 0; i < N; i++) {
524                final ConditionRecord r = automatic.get(i);
525                config.conditionComponents[i] = r.component;
526                config.conditionIds[i] = r.id;
527            }
528        }
529        config.exitCondition = mExitCondition;
530        config.exitConditionComponent = mExitConditionComponent;
531        if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config);
532        mZenModeHelper.setConfig(config);
533    }
534
535    private class ZenModeHelperCallback extends ZenModeHelper.Callback {
536        @Override
537        void onConfigChanged() {
538            loadZenConfig();
539        }
540
541        @Override
542        void onZenModeChanged() {
543            final int mode = mZenModeHelper.getZenMode();
544            if (mode == Global.ZEN_MODE_OFF) {
545                // ensure any manual condition is cleared
546                setZenModeCondition(null, "zenOff");
547            }
548        }
549    }
550
551    private class DowntimeCallback implements DowntimeConditionProvider.Callback {
552        @Override
553        public void onDowntimeChanged(int downtimeMode) {
554            final int mode = mZenModeHelper.getZenMode();
555            final ZenModeConfig config = mZenModeHelper.getConfig();
556            final boolean inDowntime = downtimeMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
557                    || downtimeMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
558            final boolean downtimeCurrent = mDowntime.isDowntimeCondition(mExitCondition);
559            // enter downtime, or update mode if reconfigured during an active downtime
560            if (inDowntime && (mode == Global.ZEN_MODE_OFF || downtimeCurrent)  && config != null) {
561                final Condition condition = mDowntime.createCondition(config.toDowntimeInfo(),
562                        Condition.STATE_TRUE);
563                mZenModeHelper.setZenMode(downtimeMode, "downtimeEnter");
564                setZenModeCondition(condition, "downtime");
565            }
566            // exit downtime
567            if (!inDowntime && downtimeCurrent && (mode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
568                    || mode == Global.ZEN_MODE_NO_INTERRUPTIONS)) {
569                mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "downtimeExit");
570            }
571        }
572
573        @Override
574        public NextAlarmTracker getNextAlarmTracker() {
575            return mNextAlarmTracker;
576        }
577    }
578
579    private class NextAlarmCallback implements NextAlarmConditionProvider.Callback {
580        @Override
581        public boolean isInDowntime() {
582            return mDowntime.isInDowntime();
583        }
584
585        @Override
586        public NextAlarmTracker getNextAlarmTracker() {
587            return mNextAlarmTracker;
588        }
589    }
590
591    private static class ConditionRecord {
592        public final Uri id;
593        public final ComponentName component;
594        public Condition condition;
595        public ManagedServiceInfo info;
596        public boolean isAutomatic;
597        public boolean isManual;
598
599        private ConditionRecord(Uri id, ComponentName component) {
600            this.id = id;
601            this.component = component;
602        }
603
604        @Override
605        public String toString() {
606            final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
607                    .append(id).append(",component=").append(component);
608            if (isAutomatic) sb.append(",automatic");
609            if (isManual) sb.append(",manual");
610            return sb.append(']').toString();
611        }
612    }
613}
614