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