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