FileDocCategorySizeDatePackage
ConditionProviders.javaAPI DocAndroid 5.1 API25901Thu Mar 12 22:22:42 GMT 2015com.android.server.notification

ConditionProviders.java

/**
 * Copyright (c) 2014, The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.notification;

import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
import android.service.notification.IConditionListener;
import android.service.notification.IConditionProvider;
import android.service.notification.ZenModeConfig;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;

import com.android.internal.R;
import com.android.server.notification.NotificationManagerService.DumpFilter;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;

public class ConditionProviders extends ManagedServices {
    private static final Condition[] NO_CONDITIONS = new Condition[0];

    private final ZenModeHelper mZenModeHelper;
    private final ArrayMap<IBinder, IConditionListener> mListeners
            = new ArrayMap<IBinder, IConditionListener>();
    private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>();
    private final ArraySet<String> mSystemConditionProviders;
    private final CountdownConditionProvider mCountdown;
    private final DowntimeConditionProvider mDowntime;
    private final NextAlarmConditionProvider mNextAlarm;
    private final NextAlarmTracker mNextAlarmTracker;

    private Condition mExitCondition;
    private ComponentName mExitConditionComponent;

    public ConditionProviders(Context context, Handler handler,
            UserProfiles userProfiles, ZenModeHelper zenModeHelper) {
        super(context, handler, new Object(), userProfiles);
        mZenModeHelper = zenModeHelper;
        mZenModeHelper.addCallback(new ZenModeHelperCallback());
        mSystemConditionProviders = safeSet(PropConfig.getStringArray(mContext,
                "system.condition.providers",
                R.array.config_system_condition_providers));
        final boolean countdown = mSystemConditionProviders.contains(ZenModeConfig.COUNTDOWN_PATH);
        final boolean downtime = mSystemConditionProviders.contains(ZenModeConfig.DOWNTIME_PATH);
        final boolean nextAlarm = mSystemConditionProviders.contains(ZenModeConfig.NEXT_ALARM_PATH);
        mNextAlarmTracker = (downtime || nextAlarm) ? new NextAlarmTracker(mContext) : null;
        mCountdown = countdown ? new CountdownConditionProvider() : null;
        mDowntime = downtime ? new DowntimeConditionProvider(this, mNextAlarmTracker,
                mZenModeHelper) : null;
        mNextAlarm = nextAlarm ? new NextAlarmConditionProvider(mNextAlarmTracker) : null;
        loadZenConfig();
    }

    public boolean isSystemConditionProviderEnabled(String path) {
        return mSystemConditionProviders.contains(path);
    }

    @Override
    protected Config getConfig() {
        Config c = new Config();
        c.caption = "condition provider";
        c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
        c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
        c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
        c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS;
        c.clientLabel = R.string.condition_provider_service_binding_label;
        return c;
    }

    @Override
    public void dump(PrintWriter pw, DumpFilter filter) {
        super.dump(pw, filter);
        synchronized(mMutex) {
            if (filter == null) {
                pw.print("    mListeners("); pw.print(mListeners.size()); pw.println("):");
                for (int i = 0; i < mListeners.size(); i++) {
                    pw.print("      "); pw.println(mListeners.keyAt(i));
                }
            }
            pw.print("    mRecords("); pw.print(mRecords.size()); pw.println("):");
            for (int i = 0; i < mRecords.size(); i++) {
                final ConditionRecord r = mRecords.get(i);
                if (filter != null && !filter.matches(r.component)) continue;
                pw.print("      "); pw.println(r);
                final String countdownDesc =  CountdownConditionProvider.tryParseDescription(r.id);
                if (countdownDesc != null) {
                    pw.print("        ("); pw.print(countdownDesc); pw.println(")");
                }
            }
        }
        pw.print("    mSystemConditionProviders: "); pw.println(mSystemConditionProviders);
        if (mCountdown != null) {
            mCountdown.dump(pw, filter);
        }
        if (mDowntime != null) {
            mDowntime.dump(pw, filter);
        }
        if (mNextAlarm != null) {
            mNextAlarm.dump(pw, filter);
        }
        if (mNextAlarmTracker != null) {
            mNextAlarmTracker.dump(pw, filter);
        }
    }

    @Override
    protected IInterface asInterface(IBinder binder) {
        return IConditionProvider.Stub.asInterface(binder);
    }

    @Override
    public void onBootPhaseAppsCanStart() {
        super.onBootPhaseAppsCanStart();
        if (mNextAlarmTracker != null) {
            mNextAlarmTracker.init();
        }
        if (mCountdown != null) {
            mCountdown.attachBase(mContext);
            registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT,
                    UserHandle.USER_OWNER);
        }
        if (mDowntime != null) {
            mDowntime.attachBase(mContext);
            registerService(mDowntime.asInterface(), DowntimeConditionProvider.COMPONENT,
                    UserHandle.USER_OWNER);
        }
        if (mNextAlarm != null) {
            mNextAlarm.attachBase(mContext);
            registerService(mNextAlarm.asInterface(), NextAlarmConditionProvider.COMPONENT,
                    UserHandle.USER_OWNER);
        }
    }

    @Override
    public void onUserSwitched() {
        super.onUserSwitched();
        if (mNextAlarmTracker != null) {
            mNextAlarmTracker.onUserSwitched();
        }
    }

    @Override
    protected void onServiceAdded(ManagedServiceInfo info) {
        final IConditionProvider provider = provider(info);
        try {
            provider.onConnected();
        } catch (RemoteException e) {
            // we tried
        }
        synchronized (mMutex) {
            if (info.component.equals(mExitConditionComponent)) {
                // ensure record exists, we'll wire it up and subscribe below
                final ConditionRecord manualRecord =
                        getRecordLocked(mExitCondition.id, mExitConditionComponent);
                manualRecord.isManual = true;
            }
            final int N = mRecords.size();
            for(int i = 0; i < N; i++) {
                final ConditionRecord r = mRecords.get(i);
                if (!r.component.equals(info.component)) continue;
                r.info = info;
                // if automatic or manual, auto-subscribe
                if (r.isAutomatic || r.isManual) {
                    subscribeLocked(r);
                }
            }
        }
    }

    @Override
    protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
        if (removed == null) return;
        for (int i = mRecords.size() - 1; i >= 0; i--) {
            final ConditionRecord r = mRecords.get(i);
            if (!r.component.equals(removed.component)) continue;
            if (r.isManual) {
                // removing the current manual condition, exit zen
                onManualConditionClearing();
                mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "manualServiceRemoved");
            }
            if (r.isAutomatic) {
                // removing an automatic condition, exit zen
                mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "automaticServiceRemoved");
            }
            mRecords.remove(i);
        }
    }

    public ManagedServiceInfo checkServiceToken(IConditionProvider provider) {
        synchronized(mMutex) {
            return checkServiceTokenLocked(provider);
        }
    }

    public void requestZenModeConditions(IConditionListener callback, int relevance) {
        synchronized(mMutex) {
            if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback
                    + " relevance=" + Condition.relevanceToString(relevance));
            if (callback == null) return;
            relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS);
            if (relevance != 0) {
                mListeners.put(callback.asBinder(), callback);
                requestConditionsLocked(relevance);
            } else {
                mListeners.remove(callback.asBinder());
                if (mListeners.isEmpty()) {
                    requestConditionsLocked(0);
                }
            }
        }
    }

    private Condition[] validateConditions(String pkg, Condition[] conditions) {
        if (conditions == null || conditions.length == 0) return null;
        final int N = conditions.length;
        final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N);
        for (int i = 0; i < N; i++) {
            final Uri id = conditions[i].id;
            if (!Condition.isValidId(id, pkg)) {
                Slog.w(TAG, "Ignoring condition from " + pkg + " for invalid id: " + id);
                continue;
            }
            if (valid.containsKey(id)) {
                Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id);
                continue;
            }
            valid.put(id, conditions[i]);
        }
        if (valid.size() == 0) return null;
        if (valid.size() == N) return conditions;
        final Condition[] rt = new Condition[valid.size()];
        for (int i = 0; i < rt.length; i++) {
            rt[i] = valid.valueAt(i);
        }
        return rt;
    }

    private ConditionRecord getRecordLocked(Uri id, ComponentName component) {
        final int N = mRecords.size();
        for (int i = 0; i < N; i++) {
            final ConditionRecord r = mRecords.get(i);
            if (r.id.equals(id) && r.component.equals(component)) {
                return r;
            }
        }
        final ConditionRecord r = new ConditionRecord(id, component);
        mRecords.add(r);
        return r;
    }

    public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
        synchronized(mMutex) {
            if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
                    + (conditions == null ? null : Arrays.asList(conditions)));
            conditions = validateConditions(pkg, conditions);
            if (conditions == null || conditions.length == 0) return;
            final int N = conditions.length;
            for (IConditionListener listener : mListeners.values()) {
                try {
                    listener.onConditionsReceived(conditions);
                } catch (RemoteException e) {
                    Slog.w(TAG, "Error sending conditions to listener " + listener, e);
                }
            }
            for (int i = 0; i < N; i++) {
                final Condition c = conditions[i];
                final ConditionRecord r = getRecordLocked(c.id, info.component);
                final Condition oldCondition = r.condition;
                final boolean conditionUpdate = oldCondition != null && !oldCondition.equals(c);
                r.info = info;
                r.condition = c;
                // if manual, exit zen if false (or failed), update if true (and changed)
                if (r.isManual) {
                    if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
                        final boolean failed = c.state == Condition.STATE_ERROR;
                        if (failed) {
                            Slog.w(TAG, "Exit zen: manual condition failed: " + c);
                        } else if (DEBUG) {
                            Slog.d(TAG, "Exit zen: manual condition false: " + c);
                        }
                        onManualConditionClearing();
                        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
                                "manualConditionExit");
                        unsubscribeLocked(r);
                        r.isManual = false;
                    } else if (c.state == Condition.STATE_TRUE && conditionUpdate) {
                        if (DEBUG) Slog.d(TAG, "Current condition updated, still true. old="
                                + oldCondition + " new=" + c);
                        setZenModeCondition(c, "conditionUpdate");
                    }
                }
                // if automatic, exit zen if false (or failed), enter zen if true
                if (r.isAutomatic) {
                    if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
                        final boolean failed = c.state == Condition.STATE_ERROR;
                        if (failed) {
                            Slog.w(TAG, "Exit zen: automatic condition failed: " + c);
                        } else if (DEBUG) {
                            Slog.d(TAG, "Exit zen: automatic condition false: " + c);
                        }
                        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
                                "automaticConditionExit");
                    } else if (c.state == Condition.STATE_TRUE) {
                        Slog.d(TAG, "Enter zen: automatic condition true: " + c);
                        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                                "automaticConditionEnter");
                    }
                }
            }
        }
    }

    private void ensureRecordExists(Condition condition, IConditionProvider provider,
            ComponentName component) {
        // constructed by convention, make sure the record exists...
        final ConditionRecord r = getRecordLocked(condition.id, component);
        if (r.info == null) {
            // ... and is associated with the in-process service
            r.info = checkServiceTokenLocked(provider);
        }
    }

    public void setZenModeCondition(Condition condition, String reason) {
        if (DEBUG) Slog.d(TAG, "setZenModeCondition " + condition + " reason=" + reason);
        synchronized(mMutex) {
            ComponentName conditionComponent = null;
            if (condition != null) {
                if (mCountdown != null && ZenModeConfig.isValidCountdownConditionId(condition.id)) {
                    ensureRecordExists(condition, mCountdown.asInterface(),
                            CountdownConditionProvider.COMPONENT);
                }
                if (mDowntime != null && ZenModeConfig.isValidDowntimeConditionId(condition.id)) {
                    ensureRecordExists(condition, mDowntime.asInterface(),
                            DowntimeConditionProvider.COMPONENT);
                }
            }
            final int N = mRecords.size();
            for (int i = 0; i < N; i++) {
                final ConditionRecord r = mRecords.get(i);
                final boolean idEqual = condition != null && r.id.equals(condition.id);
                if (r.isManual && !idEqual) {
                    // was previous manual condition, unsubscribe
                    unsubscribeLocked(r);
                    r.isManual = false;
                } else if (idEqual && !r.isManual) {
                    // is new manual condition, subscribe
                    subscribeLocked(r);
                    r.isManual = true;
                }
                if (idEqual) {
                    conditionComponent = r.component;
                }
            }
            if (!Objects.equals(mExitCondition, condition)) {
                mExitCondition = condition;
                mExitConditionComponent = conditionComponent;
                ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, reason);
                saveZenConfigLocked();
            }
        }
    }

    private void subscribeLocked(ConditionRecord r) {
        if (DEBUG) Slog.d(TAG, "subscribeLocked " + r);
        final IConditionProvider provider = provider(r);
        RemoteException re = null;
        if (provider != null) {
            try {
                Slog.d(TAG, "Subscribing to " + r.id + " with " + provider);
                provider.onSubscribe(r.id);
            } catch (RemoteException e) {
                Slog.w(TAG, "Error subscribing to " + r, e);
                re = e;
            }
        }
        ZenLog.traceSubscribe(r != null ? r.id : null, provider, re);
    }

    @SafeVarargs
    private static <T> ArraySet<T> safeSet(T... items) {
        final ArraySet<T> rt = new ArraySet<T>();
        if (items == null || items.length == 0) return rt;
        final int N = items.length;
        for (int i = 0; i < N; i++) {
            final T item = items[i];
            if (item != null) {
                rt.add(item);
            }
        }
        return rt;
    }

    public void setAutomaticZenModeConditions(Uri[] conditionIds) {
        setAutomaticZenModeConditions(conditionIds, true /*save*/);
    }

    private void setAutomaticZenModeConditions(Uri[] conditionIds, boolean save) {
        if (DEBUG) Slog.d(TAG, "setAutomaticZenModeConditions "
                + (conditionIds == null ? null : Arrays.asList(conditionIds)));
        synchronized(mMutex) {
            final ArraySet<Uri> newIds = safeSet(conditionIds);
            final int N = mRecords.size();
            boolean changed = false;
            for (int i = 0; i < N; i++) {
                final ConditionRecord r = mRecords.get(i);
                final boolean automatic = newIds.contains(r.id);
                if (!r.isAutomatic && automatic) {
                    // subscribe to new automatic
                    subscribeLocked(r);
                    r.isAutomatic = true;
                    changed = true;
                } else if (r.isAutomatic && !automatic) {
                    // unsubscribe from old automatic
                    unsubscribeLocked(r);
                    r.isAutomatic = false;
                    changed = true;
                }
            }
            if (save && changed) {
                saveZenConfigLocked();
            }
        }
    }

    public Condition[] getAutomaticZenModeConditions() {
        synchronized(mMutex) {
            final int N = mRecords.size();
            ArrayList<Condition> rt = null;
            for (int i = 0; i < N; i++) {
                final ConditionRecord r = mRecords.get(i);
                if (r.isAutomatic && r.condition != null) {
                    if (rt == null) rt = new ArrayList<Condition>();
                    rt.add(r.condition);
                }
            }
            return rt == null ? NO_CONDITIONS : rt.toArray(new Condition[rt.size()]);
        }
    }

    private void unsubscribeLocked(ConditionRecord r) {
        if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
        final IConditionProvider provider = provider(r);
        RemoteException re = null;
        if (provider != null) {
            try {
                provider.onUnsubscribe(r.id);
            } catch (RemoteException e) {
                Slog.w(TAG, "Error unsubscribing to " + r, e);
                re = e;
            }
        }
        ZenLog.traceUnsubscribe(r != null ? r.id : null, provider, re);
    }

    private static IConditionProvider provider(ConditionRecord r) {
        return r == null ? null : provider(r.info);
    }

    private static IConditionProvider provider(ManagedServiceInfo info) {
        return info == null ? null : (IConditionProvider) info.service;
    }

    private void requestConditionsLocked(int flags) {
        for (ManagedServiceInfo info : mServices) {
            final IConditionProvider provider = provider(info);
            if (provider == null) continue;
            // clear all stored conditions from this provider that we no longer care about
            for (int i = mRecords.size() - 1; i >= 0; i--) {
                final ConditionRecord r = mRecords.get(i);
                if (r.info != info) continue;
                if (r.isManual || r.isAutomatic) continue;
                mRecords.remove(i);
            }
            try {
                provider.onRequestConditions(flags);
            } catch (RemoteException e) {
                Slog.w(TAG, "Error requesting conditions from " + info.component, e);
            }
        }
    }

    private void loadZenConfig() {
        final ZenModeConfig config = mZenModeHelper.getConfig();
        if (config == null) {
            if (DEBUG) Slog.d(TAG, "loadZenConfig: no config");
            return;
        }
        synchronized (mMutex) {
            final boolean changingExit = !Objects.equals(mExitCondition, config.exitCondition);
            mExitCondition = config.exitCondition;
            mExitConditionComponent = config.exitConditionComponent;
            if (changingExit) {
                ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, "config");
            }
            if (mDowntime != null) {
                mDowntime.setConfig(config);
            }
            if (config.conditionComponents == null || config.conditionIds == null
                    || config.conditionComponents.length != config.conditionIds.length) {
                if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions");
                setAutomaticZenModeConditions(null, false /*save*/);
                return;
            }
            final ArraySet<Uri> newIds = new ArraySet<Uri>();
            final int N = config.conditionComponents.length;
            for (int i = 0; i < N; i++) {
                final ComponentName component = config.conditionComponents[i];
                final Uri id = config.conditionIds[i];
                if (component != null && id != null) {
                    getRecordLocked(id, component);  // ensure record exists
                    newIds.add(id);
                }
            }
            if (DEBUG) Slog.d(TAG, "loadZenConfig: N=" + N);
            setAutomaticZenModeConditions(newIds.toArray(new Uri[newIds.size()]), false /*save*/);
        }
    }

    private void saveZenConfigLocked() {
        ZenModeConfig config = mZenModeHelper.getConfig();
        if (config == null) return;
        config = config.copy();
        final ArrayList<ConditionRecord> automatic = new ArrayList<ConditionRecord>();
        final int automaticN = mRecords.size();
        for (int i = 0; i < automaticN; i++) {
            final ConditionRecord r = mRecords.get(i);
            if (r.isAutomatic) {
                automatic.add(r);
            }
        }
        if (automatic.isEmpty()) {
            config.conditionComponents = null;
            config.conditionIds = null;
        } else {
            final int N = automatic.size();
            config.conditionComponents = new ComponentName[N];
            config.conditionIds = new Uri[N];
            for (int i = 0; i < N; i++) {
                final ConditionRecord r = automatic.get(i);
                config.conditionComponents[i] = r.component;
                config.conditionIds[i] = r.id;
            }
        }
        config.exitCondition = mExitCondition;
        config.exitConditionComponent = mExitConditionComponent;
        if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config);
        mZenModeHelper.setConfig(config);
    }

    private void onManualConditionClearing() {
        if (mDowntime != null) {
            mDowntime.onManualConditionClearing();
        }
    }

    private class ZenModeHelperCallback extends ZenModeHelper.Callback {
        @Override
        void onConfigChanged() {
            loadZenConfig();
        }

        @Override
        void onZenModeChanged() {
            final int mode = mZenModeHelper.getZenMode();
            if (mode == Global.ZEN_MODE_OFF) {
                // ensure any manual condition is cleared
                setZenModeCondition(null, "zenOff");
            }
        }
    }

    private static class ConditionRecord {
        public final Uri id;
        public final ComponentName component;
        public Condition condition;
        public ManagedServiceInfo info;
        public boolean isAutomatic;
        public boolean isManual;

        private ConditionRecord(Uri id, ComponentName component) {
            this.id = id;
            this.component = component;
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
                    .append(id).append(",component=").append(component);
            if (isAutomatic) sb.append(",automatic");
            if (isManual) sb.append(",manual");
            return sb.append(']').toString();
        }
    }
}