ConditionProviders.java revision fb8eb00bcd56429fc6a4f1f349ee09a45074ebbf
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.app.AlarmManager;
20import android.app.PendingIntent;
21import android.content.BroadcastReceiver;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.net.Uri;
27import android.os.Handler;
28import android.os.IBinder;
29import android.os.IInterface;
30import android.os.RemoteException;
31import android.provider.Settings;
32import android.provider.Settings.Global;
33import android.service.notification.Condition;
34import android.service.notification.ConditionProviderService;
35import android.service.notification.IConditionListener;
36import android.service.notification.IConditionProvider;
37import android.service.notification.ZenModeConfig;
38import android.text.format.DateUtils;
39import android.util.ArrayMap;
40import android.util.ArraySet;
41import android.util.Slog;
42
43import com.android.internal.R;
44
45import java.io.PrintWriter;
46import java.util.ArrayList;
47import java.util.Arrays;
48import java.util.Date;
49
50public class ConditionProviders extends ManagedServices {
51    private static final Condition[] NO_CONDITIONS = new Condition[0];
52
53    private final ZenModeHelper mZenModeHelper;
54    private final ArrayMap<IBinder, IConditionListener> mListeners
55            = new ArrayMap<IBinder, IConditionListener>();
56    private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>();
57    private final CountdownConditionHelper mCountdownHelper = new CountdownConditionHelper();
58
59    public ConditionProviders(Context context, Handler handler,
60            UserProfiles userProfiles, ZenModeHelper zenModeHelper) {
61        super(context, handler, new Object(), userProfiles);
62        mZenModeHelper = zenModeHelper;
63        mZenModeHelper.addCallback(new ZenModeHelperCallback());
64        loadZenConfig();
65    }
66
67    @Override
68    protected Config getConfig() {
69        Config c = new Config();
70        c.caption = "condition provider";
71        c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
72        c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
73        c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
74        c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS;
75        c.clientLabel = R.string.condition_provider_service_binding_label;
76        return c;
77    }
78
79    @Override
80    public void dump(PrintWriter pw) {
81        super.dump(pw);
82        synchronized(mMutex) {
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            pw.print("    mRecords("); pw.print(mRecords.size()); pw.println("):");
88            for (int i = 0; i < mRecords.size(); i++) {
89                pw.print("      "); pw.println(mRecords.get(i));
90            }
91            pw.print("    mCountdownHelper: ");
92            pw.println(mCountdownHelper.getCurrentConditionDescription());
93        }
94    }
95
96    @Override
97    protected IInterface asInterface(IBinder binder) {
98        return IConditionProvider.Stub.asInterface(binder);
99    }
100
101    @Override
102    protected void onServiceAdded(ManagedServiceInfo info) {
103        Slog.d(TAG, "onServiceAdded " + info);
104        final IConditionProvider provider = provider(info);
105        try {
106            provider.onConnected();
107        } catch (RemoteException e) {
108            // we tried
109        }
110        synchronized (mMutex) {
111            final int N = mRecords.size();
112            for(int i = 0; i < N; i++) {
113                final ConditionRecord r = mRecords.get(i);
114                if (!r.component.equals(info.component)) continue;
115                r.info = info;
116                // if automatic, auto-subscribe
117                if (r.isAutomatic) {
118                    try {
119                        final Uri id = r.id;
120                        if (DEBUG) Slog.d(TAG, "Auto-subscribing to configured condition " + id);
121                        provider.onSubscribe(id);
122                    } catch (RemoteException e) {
123                        // we tried
124                    }
125                }
126            }
127        }
128    }
129
130    @Override
131    protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
132        if (removed == null) return;
133        for (int i = mRecords.size() - 1; i >= 0; i--) {
134            final ConditionRecord r = mRecords.get(i);
135            if (!r.component.equals(removed.component)) continue;
136            if (r.isManual) {
137                // removing the current manual condition, exit zen
138                mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF);
139            }
140            if (r.isAutomatic) {
141                // removing an automatic condition, exit zen
142                mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF);
143            }
144            mRecords.remove(i);
145        }
146    }
147
148    public ManagedServiceInfo checkServiceToken(IConditionProvider provider) {
149        synchronized(mMutex) {
150            return checkServiceTokenLocked(provider);
151        }
152    }
153
154    public void requestZenModeConditions(IConditionListener callback, int relevance) {
155        synchronized(mMutex) {
156            if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback
157                    + " relevance=" + Condition.relevanceToString(relevance));
158            if (callback == null) return;
159            relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS);
160            if (relevance != 0) {
161                mListeners.put(callback.asBinder(), callback);
162                requestConditionsLocked(relevance);
163            } else {
164                mListeners.remove(callback.asBinder());
165                if (mListeners.isEmpty()) {
166                    requestConditionsLocked(0);
167                }
168            }
169        }
170    }
171
172    private Condition[] validateConditions(String pkg, Condition[] conditions) {
173        if (conditions == null || conditions.length == 0) return null;
174        final int N = conditions.length;
175        final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N);
176        for (int i = 0; i < N; i++) {
177            final Uri id = conditions[i].id;
178            if (!Condition.isValidId(id, pkg)) {
179                Slog.w(TAG, "Ignoring condition from " + pkg + " for invalid id: " + id);
180                continue;
181            }
182            if (valid.containsKey(id)) {
183                Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id);
184                continue;
185            }
186            valid.put(id, conditions[i]);
187        }
188        if (valid.size() == 0) return null;
189        if (valid.size() == N) return conditions;
190        final Condition[] rt = new Condition[valid.size()];
191        for (int i = 0; i < rt.length; i++) {
192            rt[i] = valid.valueAt(i);
193        }
194        return rt;
195    }
196
197    private ConditionRecord getRecordLocked(Uri id, ComponentName component) {
198        final int N = mRecords.size();
199        for (int i = 0; i < N; i++) {
200            final ConditionRecord r = mRecords.get(i);
201            if (r.id.equals(id) && r.component.equals(component)) {
202                return r;
203            }
204        }
205        final ConditionRecord r = new ConditionRecord(id, component);
206        mRecords.add(r);
207        return r;
208    }
209
210    public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
211        synchronized(mMutex) {
212            if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
213                    + (conditions == null ? null : Arrays.asList(conditions)));
214            conditions = validateConditions(pkg, conditions);
215            if (conditions == null || conditions.length == 0) return;
216            final int N = conditions.length;
217            for (IConditionListener listener : mListeners.values()) {
218                try {
219                    listener.onConditionsReceived(conditions);
220                } catch (RemoteException e) {
221                    Slog.w(TAG, "Error sending conditions to listener " + listener, e);
222                }
223            }
224            for (int i = 0; i < N; i++) {
225                final Condition c = conditions[i];
226                final ConditionRecord r = getRecordLocked(c.id, info.component);
227                r.info = info;
228                r.condition = c;
229                // if manual, exit zen if false (or failed)
230                if (r.isManual) {
231                    if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
232                        final boolean failed = c.state == Condition.STATE_ERROR;
233                        if (failed) {
234                            Slog.w(TAG, "Exit zen: manual condition failed: " + c);
235                        } else if (DEBUG) {
236                            Slog.d(TAG, "Exit zen: manual condition false: " + c);
237                        }
238                        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF);
239                        unsubscribeLocked(r);
240                        r.isManual = false;
241                    }
242                }
243                // if automatic, exit zen if false (or failed), enter zen if true
244                if (r.isAutomatic) {
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: automatic condition failed: " + c);
249                        } else if (DEBUG) {
250                            Slog.d(TAG, "Exit zen: automatic condition false: " + c);
251                        }
252                        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF);
253                    } else if (c.state == Condition.STATE_TRUE) {
254                        Slog.d(TAG, "Enter zen: automatic condition true: " + c);
255                        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_ON);
256                    }
257                }
258            }
259        }
260    }
261
262    public void setZenModeCondition(Uri conditionId) {
263        if (DEBUG) Slog.d(TAG, "setZenModeCondition " + conditionId);
264        synchronized(mMutex) {
265            final int N = mRecords.size();
266            for (int i = 0; i < N; i++) {
267                final ConditionRecord r = mRecords.get(i);
268                final boolean idEqual = r.id.equals(conditionId);
269                if (r.isManual && !idEqual) {
270                    // was previous manual condition, unsubscribe
271                    unsubscribeLocked(r);
272                    r.isManual = false;
273                } else if (idEqual && !r.isManual) {
274                    // is new manual condition, subscribe
275                    subscribeLocked(r);
276                    r.isManual = true;
277                }
278            }
279        }
280        mCountdownHelper.setZenModeCondition(conditionId);
281    }
282
283    private void subscribeLocked(ConditionRecord r) {
284        if (DEBUG) Slog.d(TAG, "subscribeLocked " + r);
285        final IConditionProvider provider = provider(r);
286        if (provider == null) {
287            Slog.w(TAG, "subscribeLocked: no provider");
288            return;
289        }
290        try {
291            provider.onSubscribe(r.id);
292        } catch (RemoteException e) {
293            Slog.w(TAG, "Error subscribing to " + r, e);
294        }
295    }
296
297    private static <T> ArraySet<T> safeSet(T... items) {
298        final ArraySet<T> rt = new ArraySet<T>();
299        if (items == null || items.length == 0) return rt;
300        final int N = items.length;
301        for (int i = 0; i < N; i++) {
302            final T item = items[i];
303            if (item != null) {
304                rt.add(item);
305            }
306        }
307        return rt;
308    }
309
310    public void setAutomaticZenModeConditions(Uri[] conditionIds) {
311        setAutomaticZenModeConditions(conditionIds, true /*save*/);
312    }
313
314    private void setAutomaticZenModeConditions(Uri[] conditionIds, boolean save) {
315        if (DEBUG) Slog.d(TAG, "setAutomaticZenModeConditions "
316                + (conditionIds == null ? null : Arrays.asList(conditionIds)));
317        synchronized(mMutex) {
318            final ArraySet<Uri> newIds = safeSet(conditionIds);
319            final int N = mRecords.size();
320            boolean changed = false;
321            for (int i = 0; i < N; i++) {
322                final ConditionRecord r = mRecords.get(i);
323                final boolean automatic = newIds.contains(r.id);
324                if (!r.isAutomatic && automatic) {
325                    // subscribe to new automatic
326                    subscribeLocked(r);
327                    r.isAutomatic = true;
328                    changed = true;
329                } else if (r.isAutomatic && !automatic) {
330                    // unsubscribe from old automatic
331                    unsubscribeLocked(r);
332                    r.isAutomatic = false;
333                    changed = true;
334                }
335            }
336            if (save && changed) {
337                saveZenConfigLocked();
338            }
339        }
340    }
341
342    public Condition[] getAutomaticZenModeConditions() {
343        synchronized(mMutex) {
344            final int N = mRecords.size();
345            ArrayList<Condition> rt = null;
346            for (int i = 0; i < N; i++) {
347                final ConditionRecord r = mRecords.get(i);
348                if (r.isAutomatic && r.condition != null) {
349                    if (rt == null) rt = new ArrayList<Condition>();
350                    rt.add(r.condition);
351                }
352            }
353            return rt == null ? NO_CONDITIONS : rt.toArray(new Condition[rt.size()]);
354        }
355    }
356
357    private void unsubscribeLocked(ConditionRecord r) {
358        if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
359        final IConditionProvider provider = provider(r);
360        if (provider == null) {
361            Slog.w(TAG, "unsubscribeLocked: no provider");
362            return;
363        }
364        try {
365            provider.onUnsubscribe(r.id);
366        } catch (RemoteException e) {
367            Slog.w(TAG, "Error unsubscribing to " + r, e);
368        }
369    }
370
371    private static IConditionProvider provider(ConditionRecord r) {
372        return r == null ? null : provider(r.info);
373    }
374
375    private static IConditionProvider provider(ManagedServiceInfo info) {
376        return info == null ? null : (IConditionProvider) info.service;
377    }
378
379    private void requestConditionsLocked(int flags) {
380        for (ManagedServiceInfo info : mServices) {
381            final IConditionProvider provider = provider(info);
382            if (provider == null) continue;
383            try {
384                provider.onRequestConditions(flags);
385            } catch (RemoteException e) {
386                Slog.w(TAG, "Error requesting conditions from " + info.component, e);
387            }
388        }
389    }
390
391    private void loadZenConfig() {
392        final ZenModeConfig config = mZenModeHelper.getConfig();
393        if (config == null) {
394            if (DEBUG) Slog.d(TAG, "loadZenConfig: no config");
395            return;
396        }
397        synchronized (mMutex) {
398            if (config.conditionComponents == null || config.conditionIds == null
399                    || config.conditionComponents.length != config.conditionIds.length) {
400                if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions");
401                setAutomaticZenModeConditions(null, false /*save*/);
402                return;
403            }
404            final ArraySet<Uri> newIds = new ArraySet<Uri>();
405            final int N = config.conditionComponents.length;
406            for (int i = 0; i < N; i++) {
407                final ComponentName component = config.conditionComponents[i];
408                final Uri id = config.conditionIds[i];
409                if (component != null && id != null) {
410                    getRecordLocked(id, component);  // ensure record exists
411                    newIds.add(id);
412                }
413            }
414            if (DEBUG) Slog.d(TAG, "loadZenConfig: N=" + N);
415            setAutomaticZenModeConditions(newIds.toArray(new Uri[newIds.size()]), false /*save*/);
416        }
417    }
418
419    private void saveZenConfigLocked() {
420        ZenModeConfig config = mZenModeHelper.getConfig();
421        if (config == null) return;
422        config = config.copy();
423        final ArrayList<ConditionRecord> automatic = new ArrayList<ConditionRecord>();
424        final int automaticN = mRecords.size();
425        for (int i = 0; i < automaticN; i++) {
426            final ConditionRecord r = mRecords.get(i);
427            if (r.isAutomatic) {
428                automatic.add(r);
429            }
430        }
431        if (automatic.isEmpty()) {
432            config.conditionComponents = null;
433            config.conditionIds = null;
434        } else {
435            final int N = automatic.size();
436            config.conditionComponents = new ComponentName[N];
437            config.conditionIds = new Uri[N];
438            for (int i = 0; i < N; i++) {
439                final ConditionRecord r = automatic.get(i);
440                config.conditionComponents[i] = r.component;
441                config.conditionIds[i] = r.id;
442            }
443        }
444        if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config);
445        mZenModeHelper.setConfig(config);
446    }
447
448    private final class CountdownConditionHelper extends BroadcastReceiver {
449        private static final String ACTION = "CountdownConditionHelper";
450        private static final int REQUEST_CODE = 100;
451        private static final String EXTRA_TIME = "time";
452
453        private long mCurrent;
454
455        public CountdownConditionHelper() {
456            mContext.registerReceiver(this, new IntentFilter(ACTION));
457        }
458
459        public void setZenModeCondition(Uri conditionId) {
460            final long time = tryParseCondition(conditionId);
461            final AlarmManager alarms = (AlarmManager)
462                    mContext.getSystemService(Context.ALARM_SERVICE);
463            final Intent intent = new Intent(ACTION).putExtra(EXTRA_TIME, time)
464                    .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
465            final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE,
466                    intent, PendingIntent.FLAG_UPDATE_CURRENT);
467            alarms.cancel(pendingIntent);
468            mCurrent = time;
469            if (time > 0) {
470                final long now = System.currentTimeMillis();
471                final CharSequence span =
472                        DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS);
473                Slog.d(TAG, String.format("Scheduling %s for %s, %s in the future (%s), now=%s",
474                        ACTION, ts(time), time - now, span, ts(now)));
475                alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
476            }
477        }
478
479        public String getCurrentConditionDescription() {
480            if (mCurrent == 0) return null;
481            final long time = mCurrent;
482            final long now = System.currentTimeMillis();
483            final CharSequence span =
484                    DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS);
485            return String.format("Scheduled for %s, %s in the future (%s), now=%s",
486                    ts(time), time - now, span, ts(now));
487        }
488
489        private String ts(long time) {
490            return new Date(time) + " (" + time + ")";
491        }
492
493        @Override
494        public void onReceive(Context context, Intent intent) {
495            if (ACTION.equals(intent.getAction())) {
496                final long time = intent.getLongExtra(EXTRA_TIME, 0);
497                Slog.d(TAG, "Countdown condition fired. time=" + time + " mCurrent=" + mCurrent);
498                if (time > 0 && time == mCurrent) {
499                    // countdown condition is still the manual condition, leave zen
500                    mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF);
501                    ConditionProviders.this.setZenModeCondition(null);
502                }
503            }
504        }
505
506        private long tryParseCondition(Uri conditionId) {
507            // condition://android/countdown/1399917958951
508            if (!Condition.isValidId(conditionId, "android")) return 0;
509            if (conditionId.getPathSegments().size() != 2
510                    || !"countdown".equals(conditionId.getPathSegments().get(0))) return 0;
511            try {
512                return Long.parseLong(conditionId.getPathSegments().get(1));
513            } catch (RuntimeException e) {
514                Slog.w(TAG, "Error parsing countdown condition: " + conditionId, e);
515                return 0;
516            }
517        }
518    }
519
520    private class ZenModeHelperCallback extends ZenModeHelper.Callback {
521        @Override
522        void onConfigChanged() {
523            loadZenConfig();
524        }
525
526        @Override
527        void onZenModeChanged() {
528            final int mode = mZenModeHelper.getZenMode();
529            if (mode == Global.ZEN_MODE_OFF) {
530                // ensure any manual condition is cleared
531                setZenModeCondition(null);
532            }
533        }
534    }
535
536    private static class ConditionRecord {
537        public final Uri id;
538        public final ComponentName component;
539        public Condition condition;
540        public ManagedServiceInfo info;
541        public boolean isAutomatic;
542        public boolean isManual;
543
544        private ConditionRecord(Uri id, ComponentName component) {
545            this.id = id;
546            this.component = component;
547        }
548
549        @Override
550        public String toString() {
551            final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
552                    .append(id).append(",component=").append(component);
553            if (isAutomatic) sb.append(",automatic");
554            if (isManual) sb.append(",manual");
555            return sb.append(']').toString();
556        }
557    }
558}
559