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