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