ConditionProviders.java revision 50806fc4ceff4bb093a18bdecb506163e68b9cbb
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) {
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                saveZenConfigLocked();
310            }
311        }
312    }
313
314    private void subscribeLocked(ConditionRecord r) {
315        if (DEBUG) Slog.d(TAG, "subscribeLocked " + r);
316        final IConditionProvider provider = provider(r);
317        if (provider == null) {
318            Slog.w(TAG, "subscribeLocked: no provider");
319            return;
320        }
321        try {
322            provider.onSubscribe(r.id);
323        } catch (RemoteException e) {
324            Slog.w(TAG, "Error subscribing to " + r, e);
325        }
326    }
327
328    private static <T> ArraySet<T> safeSet(T... items) {
329        final ArraySet<T> rt = new ArraySet<T>();
330        if (items == null || items.length == 0) return rt;
331        final int N = items.length;
332        for (int i = 0; i < N; i++) {
333            final T item = items[i];
334            if (item != null) {
335                rt.add(item);
336            }
337        }
338        return rt;
339    }
340
341    public void setAutomaticZenModeConditions(Uri[] conditionIds) {
342        setAutomaticZenModeConditions(conditionIds, true /*save*/);
343    }
344
345    private void setAutomaticZenModeConditions(Uri[] conditionIds, boolean save) {
346        if (DEBUG) Slog.d(TAG, "setAutomaticZenModeConditions "
347                + (conditionIds == null ? null : Arrays.asList(conditionIds)));
348        synchronized(mMutex) {
349            final ArraySet<Uri> newIds = safeSet(conditionIds);
350            final int N = mRecords.size();
351            boolean changed = false;
352            for (int i = 0; i < N; i++) {
353                final ConditionRecord r = mRecords.get(i);
354                final boolean automatic = newIds.contains(r.id);
355                if (!r.isAutomatic && automatic) {
356                    // subscribe to new automatic
357                    subscribeLocked(r);
358                    r.isAutomatic = true;
359                    changed = true;
360                } else if (r.isAutomatic && !automatic) {
361                    // unsubscribe from old automatic
362                    unsubscribeLocked(r);
363                    r.isAutomatic = false;
364                    changed = true;
365                }
366            }
367            if (save && changed) {
368                saveZenConfigLocked();
369            }
370        }
371    }
372
373    public Condition[] getAutomaticZenModeConditions() {
374        synchronized(mMutex) {
375            final int N = mRecords.size();
376            ArrayList<Condition> rt = null;
377            for (int i = 0; i < N; i++) {
378                final ConditionRecord r = mRecords.get(i);
379                if (r.isAutomatic && r.condition != null) {
380                    if (rt == null) rt = new ArrayList<Condition>();
381                    rt.add(r.condition);
382                }
383            }
384            return rt == null ? NO_CONDITIONS : rt.toArray(new Condition[rt.size()]);
385        }
386    }
387
388    private void unsubscribeLocked(ConditionRecord r) {
389        if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
390        final IConditionProvider provider = provider(r);
391        if (provider == null) {
392            Slog.w(TAG, "unsubscribeLocked: no provider");
393            return;
394        }
395        try {
396            provider.onUnsubscribe(r.id);
397        } catch (RemoteException e) {
398            Slog.w(TAG, "Error unsubscribing to " + r, e);
399        }
400    }
401
402    private static IConditionProvider provider(ConditionRecord r) {
403        return r == null ? null : provider(r.info);
404    }
405
406    private static IConditionProvider provider(ManagedServiceInfo info) {
407        return info == null ? null : (IConditionProvider) info.service;
408    }
409
410    private void requestConditionsLocked(int flags) {
411        for (ManagedServiceInfo info : mServices) {
412            final IConditionProvider provider = provider(info);
413            if (provider == null) continue;
414            // clear all stored conditions from this provider that we no longer care about
415            for (int i = mRecords.size() - 1; i >= 0; i--) {
416                final ConditionRecord r = mRecords.get(i);
417                if (r.info != info) continue;
418                if (r.isManual || r.isAutomatic) continue;
419                mRecords.remove(i);
420            }
421            try {
422                provider.onRequestConditions(flags);
423            } catch (RemoteException e) {
424                Slog.w(TAG, "Error requesting conditions from " + info.component, e);
425            }
426        }
427    }
428
429    private void loadZenConfig() {
430        final ZenModeConfig config = mZenModeHelper.getConfig();
431        if (config == null) {
432            if (DEBUG) Slog.d(TAG, "loadZenConfig: no config");
433            return;
434        }
435        synchronized (mMutex) {
436            mExitConditionId = config.exitConditionId;
437            mExitConditionComponent = config.exitConditionComponent;
438            if (config.conditionComponents == null || config.conditionIds == null
439                    || config.conditionComponents.length != config.conditionIds.length) {
440                if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions");
441                setAutomaticZenModeConditions(null, false /*save*/);
442                return;
443            }
444            final ArraySet<Uri> newIds = new ArraySet<Uri>();
445            final int N = config.conditionComponents.length;
446            for (int i = 0; i < N; i++) {
447                final ComponentName component = config.conditionComponents[i];
448                final Uri id = config.conditionIds[i];
449                if (component != null && id != null) {
450                    getRecordLocked(id, component);  // ensure record exists
451                    newIds.add(id);
452                }
453            }
454            if (DEBUG) Slog.d(TAG, "loadZenConfig: N=" + N);
455            setAutomaticZenModeConditions(newIds.toArray(new Uri[newIds.size()]), false /*save*/);
456        }
457    }
458
459    private void saveZenConfigLocked() {
460        ZenModeConfig config = mZenModeHelper.getConfig();
461        if (config == null) return;
462        config = config.copy();
463        final ArrayList<ConditionRecord> automatic = new ArrayList<ConditionRecord>();
464        final int automaticN = mRecords.size();
465        for (int i = 0; i < automaticN; i++) {
466            final ConditionRecord r = mRecords.get(i);
467            if (r.isAutomatic) {
468                automatic.add(r);
469            }
470        }
471        if (automatic.isEmpty()) {
472            config.conditionComponents = null;
473            config.conditionIds = null;
474        } else {
475            final int N = automatic.size();
476            config.conditionComponents = new ComponentName[N];
477            config.conditionIds = new Uri[N];
478            for (int i = 0; i < N; i++) {
479                final ConditionRecord r = automatic.get(i);
480                config.conditionComponents[i] = r.component;
481                config.conditionIds[i] = r.id;
482            }
483        }
484        config.exitConditionId = mExitConditionId;
485        config.exitConditionComponent = mExitConditionComponent;
486        if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config);
487        mZenModeHelper.setConfig(config);
488    }
489
490    private class ZenModeHelperCallback extends ZenModeHelper.Callback {
491        @Override
492        void onConfigChanged() {
493            loadZenConfig();
494        }
495
496        @Override
497        void onZenModeChanged() {
498            final int mode = mZenModeHelper.getZenMode();
499            if (mode == Global.ZEN_MODE_OFF) {
500                // ensure any manual condition is cleared
501                setZenModeCondition(null);
502            }
503        }
504    }
505
506    private static class ConditionRecord {
507        public final Uri id;
508        public final ComponentName component;
509        public Condition condition;
510        public ManagedServiceInfo info;
511        public boolean isAutomatic;
512        public boolean isManual;
513
514        private ConditionRecord(Uri id, ComponentName component) {
515            this.id = id;
516            this.component = component;
517        }
518
519        @Override
520        public String toString() {
521            final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
522                    .append(id).append(",component=").append(component);
523            if (isAutomatic) sb.append(",automatic");
524            if (isManual) sb.append(",manual");
525            return sb.append(']').toString();
526        }
527    }
528}
529