Introduce condition provider services.

Add the condition provider interface, base class, and associated
system metadata.

Pull out common service management code into a reusable helper,
used by notification listeners and condition providers. The
helper, ManagedServices, is now completely self-contained - it
has no dependencies on NoMan or NoMan abstractions.

Bug:13743109
Change-Id: I6856d40f0a2ead78ac9b5707568559a57e7eb009
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 8681f5c..045fab1 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -22,6 +22,8 @@
 import android.app.Notification;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.service.notification.Condition;
+import android.service.notification.IConditionProvider;
 import android.service.notification.INotificationListener;
 import android.service.notification.ZenModeConfig;
 
@@ -53,4 +55,5 @@
 
     ZenModeConfig getZenModeConfig();
     boolean setZenModeConfig(in ZenModeConfig config);
+    void notifyCondition(in IConditionProvider provider, in Condition condition);
 }
\ No newline at end of file
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7f9f862..f6d9e84 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -723,6 +723,13 @@
             = "android.settings.NOTIFICATION_LISTENER_SETTINGS";
 
     /**
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CONDITION_PROVIDER_SETTINGS
+            = "android.settings.ACTION_CONDITION_PROVIDER_SETTINGS";
+
+    /**
      * Activity Action: Show settings for video captioning.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you safeguard
@@ -4510,6 +4517,11 @@
          */
         public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
 
+        /**
+         * @hide
+         */
+        public static final String ENABLED_CONDITION_PROVIDERS = "enabled_condition_providers";
+
         /** @hide */
         public static final String BAR_SERVICE_COMPONENT = "bar_service_component";
 
diff --git a/core/java/android/service/notification/Condition.aidl b/core/java/android/service/notification/Condition.aidl
new file mode 100644
index 0000000..432852c
--- /dev/null
+++ b/core/java/android/service/notification/Condition.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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 android.service.notification;
+
+parcelable Condition;
+
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
new file mode 100644
index 0000000..cfd40f3
--- /dev/null
+++ b/core/java/android/service/notification/Condition.java
@@ -0,0 +1,119 @@
+/**
+ * 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 android.service.notification;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Condition information from condition providers.
+ *
+ * @hide
+ */
+public class Condition implements Parcelable {
+
+    public static final int FLAG_RELEVANT_NOW = 1 << 0;
+    public static final int FLAG_RELEVANT_ALWAYS = 1 << 1;
+
+    public final Uri id;
+    public String caption;
+    public boolean state;
+    public int flags;
+
+
+    public Condition(Uri id, String caption, boolean state, int flags) {
+        if (id == null) throw new IllegalArgumentException("id is required");
+        if (caption == null) throw new IllegalArgumentException("caption is required");
+        this.id = id;
+        this.caption = caption;
+        this.state = state;
+        this.flags = flags;
+    }
+
+    private Condition(Parcel source) {
+        id = Uri.CREATOR.createFromParcel(source);
+        caption = source.readString();
+        state = source.readInt() == 1;
+        flags = source.readInt();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(id, 0);
+        dest.writeString(caption);
+        dest.writeInt(state ? 1 : 0);
+        dest.writeInt(flags);
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder(Condition.class.getSimpleName()).append('[')
+            .append("id=").append(id)
+            .append(",caption=").append(caption)
+            .append(",state=").append(state)
+            .append(",flags=").append(flags)
+            .append(']').toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof Condition)) return false;
+        if (o == this) return true;
+        final Condition other = (Condition) o;
+        return Objects.equals(other.id, id)
+                && Objects.equals(other.caption, caption)
+                && other.state == state
+                && other.flags == flags;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, caption, state, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public Condition copy() {
+        final Parcel parcel = Parcel.obtain();
+        try {
+            writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            return new Condition(parcel);
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+    public static final Parcelable.Creator<Condition> CREATOR
+            = new Parcelable.Creator<Condition>() {
+        @Override
+        public Condition createFromParcel(Parcel source) {
+            return new Condition(source);
+        }
+
+        @Override
+        public Condition[] newArray(int size) {
+            return new Condition[size];
+        }
+    };
+}
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
new file mode 100644
index 0000000..8777e50
--- /dev/null
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -0,0 +1,141 @@
+/*
+ * 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 android.service.notification;
+
+import android.annotation.SdkConstant;
+import android.app.INotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.util.Log;
+
+/**
+ * A service that provides conditions about boolean state.
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * the {@link android.Manifest.permission#BIND_CONDITION_PROVIDER_SERVICE} permission
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
+ * <pre>
+ * &lt;service android:name=".MyConditionProvider"
+ *          android:label="&#64;string/service_name"
+ *          android:permission="android.permission.BIND_CONDITION_PROVIDER_SERVICE">
+ *     &lt;intent-filter>
+ *         &lt;action android:name="android.service.notification.ConditionProviderService" />
+ *     &lt;/intent-filter>
+ * &lt;/service></pre>
+ *
+ * @hide
+ */
+public abstract class ConditionProviderService extends Service {
+    private final String TAG = ConditionProviderService.class.getSimpleName()
+            + "[" + getClass().getSimpleName() + "]";
+
+    private Provider mProvider;
+    private INotificationManager mNoMan;
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE
+            = "android.service.notification.ConditionProviderService";
+
+
+    abstract public Condition[] queryConditions(int relevance);
+    abstract public Condition[] getConditions(Uri[] conditionIds);
+    abstract public boolean subscribe(Uri conditionId);
+    abstract public boolean unsubscribe(Uri conditionId);
+
+    private final INotificationManager getNotificationInterface() {
+        if (mNoMan == null) {
+            mNoMan = INotificationManager.Stub.asInterface(
+                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+        }
+        return mNoMan;
+    }
+
+    public final void notifyCondition(Condition condition) {
+        if (!isBound()) return;
+        try {
+            getNotificationInterface().notifyCondition(mProvider, condition);
+        } catch (android.os.RemoteException ex) {
+            Log.v(TAG, "Unable to contact notification manager", ex);
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (mProvider == null) {
+            mProvider = new Provider();
+        }
+        return mProvider;
+    }
+
+    private boolean isBound() {
+        if (mProvider == null) {
+            Log.w(TAG, "Condition provider service not yet bound.");
+            return false;
+        }
+        return true;
+    }
+
+    private final class Provider extends IConditionProvider.Stub {
+        private final ConditionProviderService mService = ConditionProviderService.this;
+
+        @Override
+        public Condition[] queryConditions(int relevance) {
+            try {
+                return mService.queryConditions(relevance);
+            } catch (Throwable t) {
+                Log.w(TAG, "Error running queryConditions", t);
+                return null;
+            }
+        }
+
+        @Override
+        public Condition[] getConditions(Uri[] conditionIds) {
+            try {
+                return mService.getConditions(conditionIds);
+            } catch (Throwable t) {
+                Log.w(TAG, "Error running getConditions", t);
+                return null;
+            }
+        }
+
+        @Override
+        public boolean subscribe(Uri conditionId) {
+            try {
+                return mService.subscribe(conditionId);
+            } catch (Throwable t) {
+                Log.w(TAG, "Error running subscribe", t);
+                return false;
+            }
+        }
+
+        @Override
+        public boolean unsubscribe(Uri conditionId) {
+            try {
+                return mService.unsubscribe(conditionId);
+            } catch (Throwable t) {
+                Log.w(TAG, "Error running unsubscribe", t);
+                return false;
+            }
+        }
+    }
+}
diff --git a/core/java/android/service/notification/IConditionProvider.aidl b/core/java/android/service/notification/IConditionProvider.aidl
new file mode 100644
index 0000000..cb582da
--- /dev/null
+++ b/core/java/android/service/notification/IConditionProvider.aidl
@@ -0,0 +1,28 @@
+/**
+ * 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 android.service.notification;
+
+import android.net.Uri;
+import android.service.notification.Condition;
+
+/** @hide */
+interface IConditionProvider {
+    Condition[] queryConditions(int relevance);
+    Condition[] getConditions(in Uri[] conditionIds);
+    boolean subscribe(in Uri conditionId);
+    boolean unsubscribe(in Uri conditionId);
+}
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 57e845f..ecb0295 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2620,6 +2620,15 @@
         android:description="@string/permdesc_bindNotificationListenerService"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by an {@link
+         android.service.notification.ConditionProviderService},
+         to ensure that only the system can bind to it.
+         @hide -->
+    <permission android:name="android.permission.BIND_CONDITION_PROVIDER_SERVICE"
+        android:label="@string/permlab_bindConditionProviderService"
+        android:description="@string/permdesc_bindConditionProviderService"
+        android:protectionLevel="signature" />
+
     <!-- Allows an application to call into a carrier setup flow. It is up to the
          carrier setup application to enforce that this permission is required
          @hide This is not a third-party API (intended for OEMs and system apps). -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6d4ceef..7f54837 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2048,6 +2048,11 @@
     <string name="permdesc_bindNotificationListenerService">Allows the holder to bind to the top-level interface of a notification listener service. Should never be needed for normal apps.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_bindConditionProviderService">bind to a condition provider service</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_bindConditionProviderService">Allows the holder to bind to the top-level interface of a condition provider service. Should never be needed for normal apps.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_invokeCarrierSetup">invoke the carrier-provided configuration app</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_invokeCarrierSetup">Allows the holder to invoke the carrier-provided configuration app. Should never be needed for normal apps.</string>
@@ -3797,6 +3802,8 @@
     <!-- Label to show for a service that is running because it is observing
          the user's notifications. -->
     <string name="notification_listener_binding_label">Notification listener</string>
+    <!-- Label to show for a service that is running because it is providing conditions. -->
+    <string name="condition_provider_service_binding_label">Condition provider</string>
 
     <!-- Do Not Translate: Alternate eri.xml -->
     <string name="alternate_eri_file">/data/eri.xml</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index bb0d184..431dab8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1580,6 +1580,7 @@
   <java-symbol type="string" name="low_internal_storage_view_text" />
   <java-symbol type="string" name="low_internal_storage_view_title" />
   <java-symbol type="string" name="notification_listener_binding_label" />
+  <java-symbol type="string" name="condition_provider_service_binding_label" />
   <java-symbol type="string" name="report" />
   <java-symbol type="string" name="select_input_method" />
   <java-symbol type="string" name="select_keyboard_layout_notification_title" />