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