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