Implement long-term condition subscriptions.
Bind long-term conditions (like "in a meeting") to enter/exit
zen mode automatically.
Persist automatic condition subscriptions to maintain them across
reboots.
Normalize condition state binding: true => enter zen, false => exit.
Change-Id: Icba2b8b25c0a352ae8215f4c0a324e4f966c0165
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 5567944..d074565 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
+import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
@@ -28,31 +29,31 @@
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 libcore.util.Objects;
-
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
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 ArrayMap<Uri, ManagedServiceInfo> mConditions
- = new ArrayMap<Uri, ManagedServiceInfo>();
-
- private Uri mCurrentConditionId;
+ private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>();
public ConditionProviders(Context context, Handler handler,
UserProfiles userProfiles, ZenModeHelper zenModeHelper) {
super(context, handler, new Object(), userProfiles);
mZenModeHelper = zenModeHelper;
mZenModeHelper.addCallback(new ZenModeHelperCallback());
+ loadZenConfig();
}
@Override
@@ -71,20 +72,13 @@
public void dump(PrintWriter pw) {
super.dump(pw);
synchronized(mMutex) {
- pw.print(" mCurrentConditionId="); pw.println(mCurrentConditionId);
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(" mConditions("); pw.print(mConditions.size()); pw.println("):");
- for (int i = 0; i < mConditions.size(); i++) {
- pw.print(" "); pw.print(mConditions.keyAt(i));
- final ManagedServiceInfo info = mConditions.valueAt(i);
- pw.print(" -> "); pw.print(info.component);
- if (!mServices.contains(info)) {
- pw.print(" (orphan)");
- }
- pw.println();
+ pw.print(" mRecords("); pw.print(mRecords.size()); pw.println("):");
+ for (int i = 0; i < mRecords.size(); i++) {
+ pw.print(" "); pw.println(mRecords.get(i));
}
}
}
@@ -95,29 +89,49 @@
}
@Override
- protected void onServiceAdded(IInterface service) {
- Slog.d(TAG, "onServiceAdded " + service);
- final IConditionProvider provider = (IConditionProvider) service;
+ protected void onServiceAdded(ManagedServiceInfo info) {
+ Slog.d(TAG, "onServiceAdded " + info);
+ final IConditionProvider provider = provider(info);
try {
provider.onConnected();
} catch (RemoteException e) {
// we tried
}
+ synchronized (mMutex) {
+ 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, auto-subscribe
+ if (r.isAutomatic) {
+ try {
+ final Uri id = r.id;
+ if (DEBUG) Slog.d(TAG, "Auto-subscribing to configured condition " + id);
+ provider.onSubscribe(id);
+ } catch (RemoteException e) {
+ // we tried
+ }
+ }
+ }
+ }
}
@Override
protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
if (removed == null) return;
- if (mCurrentConditionId != null) {
- if (removed.equals(mConditions.get(mCurrentConditionId))) {
- mCurrentConditionId = null;
+ 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
mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF);
}
- }
- for (int i = mConditions.size() - 1; i >= 0; i--) {
- if (removed.equals(mConditions.valueAt(i))) {
- mConditions.removeAt(i);
+ if (r.isAutomatic) {
+ // removing an automatic condition, exit zen
+ mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF);
}
+ mRecords.remove(i);
}
}
@@ -127,14 +141,15 @@
}
}
- public void requestZenModeConditions(IConditionListener callback, boolean requested) {
+ public void requestZenModeConditions(IConditionListener callback, int relevance) {
synchronized(mMutex) {
if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback
- + " requested=" + requested);
+ + " relevance=" + Condition.relevanceToString(relevance));
if (callback == null) return;
- if (requested) {
+ relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS);
+ if (relevance != 0) {
mListeners.put(callback.asBinder(), callback);
- requestConditionsLocked(Condition.FLAG_RELEVANT_NOW);
+ requestConditionsLocked(relevance);
} else {
mListeners.remove(callback.asBinder());
if (mListeners.isEmpty()) {
@@ -144,25 +159,51 @@
}
}
+ 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;
- boolean valid = true;
- for (int i = 0; i < N; i++) {
- final Uri id = conditions[i].id;
- if (!Condition.isValidId(id, pkg)) {
- Slog.w(TAG, "Ignoring conditions from " + pkg + " for invalid id: " + id);
- valid = false;
- }
- }
- if (!valid) return;
-
- for (int i = 0; i < N; i++) {
- mConditions.put(conditions[i].id, info);
- }
for (IConditionListener listener : mListeners.values()) {
try {
listener.onConditionsReceived(conditions);
@@ -170,62 +211,154 @@
Slog.w(TAG, "Error sending conditions to listener " + listener, e);
}
}
- if (mCurrentConditionId != null) {
- for (int i = 0; i < N; i++) {
- final Condition c = conditions[i];
- if (!c.id.equals(mCurrentConditionId)) continue;
- if (c.state == Condition.STATE_TRUE || c.state == Condition.STATE_ERROR) {
- triggerExitLocked(c.state == Condition.STATE_ERROR);
- return;
+ for (int i = 0; i < N; i++) {
+ final Condition c = conditions[i];
+ final ConditionRecord r = getRecordLocked(c.id, info.component);
+ r.info = info;
+ r.condition = c;
+ // if manual, exit zen if false (or failed)
+ 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);
+ }
+ mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF);
+ unsubscribeLocked(r);
+ r.isManual = false;
+ }
+ }
+ // 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);
+ } else if (c.state == Condition.STATE_TRUE) {
+ Slog.d(TAG, "Enter zen: automatic condition true: " + c);
+ mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_ON);
}
}
}
}
}
- private void triggerExitLocked(boolean error) {
- if (error) {
- Slog.w(TAG, "Zen mode exit condition failed");
- } else if (DEBUG) {
- Slog.d(TAG, "Zen mode exit condition triggered");
- }
- mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF);
- unsubscribeLocked(mCurrentConditionId);
- mCurrentConditionId = null;
- }
-
public void setZenModeCondition(Uri conditionId) {
+ if (DEBUG) Slog.d(TAG, "setZenModeCondition " + conditionId);
synchronized(mMutex) {
- if (DEBUG) Slog.d(TAG, "setZenModeCondition " + conditionId);
- if (Objects.equal(mCurrentConditionId, conditionId)) return;
-
- if (mCurrentConditionId != null) {
- unsubscribeLocked(mCurrentConditionId);
- }
- if (conditionId != null) {
- final ManagedServiceInfo info = mConditions.get(conditionId);
- final IConditionProvider provider = provider(info);
- if (provider == null) return;
- try {
- provider.onSubscribe(conditionId);
- } catch (RemoteException e) {
- Slog.w(TAG, "Error subscribing to " + conditionId
- + " from " + info.component, e);
+ final int N = mRecords.size();
+ for (int i = 0; i < N; i++) {
+ final ConditionRecord r = mRecords.get(i);
+ final boolean idEqual = r.id.equals(conditionId);
+ 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;
}
}
- mCurrentConditionId = conditionId;
}
}
- private void unsubscribeLocked(Uri conditionId) {
- final ManagedServiceInfo info = mConditions.get(mCurrentConditionId);
- final IConditionProvider provider = provider(info);
- if (provider == null) return;
- try {
- provider.onUnsubscribe(conditionId);
- } catch (RemoteException e) {
- Slog.w(TAG, "Error unsubscribing to " + conditionId + " from " + info.component, e);
+ private void subscribeLocked(ConditionRecord r) {
+ if (DEBUG) Slog.d(TAG, "subscribeLocked " + r);
+ final IConditionProvider provider = provider(r);
+ if (provider == null) {
+ Slog.w(TAG, "subscribeLocked: no provider");
+ return;
}
+ try {
+ provider.onSubscribe(r.id);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error subscribing to " + r, e);
+ }
+ }
+
+ 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);
+ if (provider == null) {
+ Slog.w(TAG, "unsubscribeLocked: no provider");
+ return;
+ }
+ try {
+ provider.onUnsubscribe(r.id);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error unsubscribing to " + r, e);
+ }
+ }
+
+ private static IConditionProvider provider(ConditionRecord r) {
+ return r == null ? null : provider(r.info);
}
private static IConditionProvider provider(ManagedServiceInfo info) {
@@ -244,20 +377,99 @@
}
}
+ private void loadZenConfig() {
+ final ZenModeConfig config = mZenModeHelper.getConfig();
+ if (config == null) {
+ if (DEBUG) Slog.d(TAG, "loadZenConfig: no config");
+ return;
+ }
+ synchronized (mMutex) {
+ 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;
+ }
+ }
+ if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config);
+ mZenModeHelper.setConfig(config);
+ }
+
private class ZenModeHelperCallback extends ZenModeHelper.Callback {
@Override
+ void onConfigChanged() {
+ loadZenConfig();
+ }
+
+ @Override
void onZenModeChanged() {
final int mode = mZenModeHelper.getZenMode();
if (mode == Global.ZEN_MODE_OFF) {
- synchronized (mMutex) {
- if (mCurrentConditionId != null) {
- if (DEBUG) Slog.d(TAG, "Zen mode off, forcing unsubscribe from "
- + mCurrentConditionId);
- unsubscribeLocked(mCurrentConditionId);
- mCurrentConditionId = null;
- }
- }
+ // ensure any manual condition is cleared
+ setZenModeCondition(null);
}
}
}
+
+ 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();
+ }
+ }
}