ConditionProviderService.java revision 38e6ca4e7481f3b1c8072705bfd38b8b454ccfe7
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 android.service.notification;
18
19import android.annotation.SdkConstant;
20import android.annotation.SystemApi;
21import android.app.INotificationManager;
22import android.app.Service;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.net.Uri;
27import android.os.Handler;
28import android.os.IBinder;
29import android.os.Message;
30import android.os.RemoteException;
31import android.os.ServiceManager;
32import android.util.Log;
33
34/**
35 * A service that provides conditions about boolean state.
36 * <p>To extend this class, you must declare the service in your manifest file with
37 * the {@link android.Manifest.permission#BIND_CONDITION_PROVIDER_SERVICE} permission
38 * and include an intent filter with the {@link #SERVICE_INTERFACE} action. If you want users to be
39 * able to create and update conditions for this service to monitor, include the
40 * {@link #META_DATA_RULE_TYPE} and {@link #META_DATA_CONFIGURATION_ACTIVITY} tags and request the
41 * {@link android.Manifest.permission#ACCESS_NOTIFICATION_POLICY} permission. For example:</p>
42 * <pre>
43 * &lt;service android:name=".MyConditionProvider"
44 *          android:label="&#64;string/service_name"
45 *          android:permission="android.permission.BIND_CONDITION_PROVIDER_SERVICE">
46 *     &lt;intent-filter>
47 *         &lt;action android:name="android.service.notification.ConditionProviderService" />
48 *     &lt;/intent-filter>
49 *     &lt;meta-data
50 *               android:name="android.service.zen.automatic.ruleType"
51 *               android:value="@string/my_condition_rule">
52 *           &lt;/meta-data>
53 *           &lt;meta-data
54 *               android:name="android.service.zen.automatic.configurationActivity"
55 *               android:value="com.my.package/.MyConditionConfigurationActivity">
56 *           &lt;/meta-data>
57 * &lt;/service></pre>
58 *
59 */
60public abstract class ConditionProviderService extends Service {
61    private final String TAG = ConditionProviderService.class.getSimpleName()
62            + "[" + getClass().getSimpleName() + "]";
63
64    private final H mHandler = new H();
65
66    private Provider mProvider;
67    private INotificationManager mNoMan;
68
69    /**
70     * The {@link Intent} that must be declared as handled by the service.
71     */
72    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
73    public static final String SERVICE_INTERFACE
74            = "android.service.notification.ConditionProviderService";
75
76    /**
77     * The name of the {@code meta-data} tag containing a localized name of the type of zen rules
78     * provided by this service.
79     */
80    public static final String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType";
81
82    /**
83     * The name of the {@code meta-data} tag containing the {@link ComponentName} of an activity
84     * that allows users to configure the conditions provided by this service.
85     */
86    public static final String META_DATA_CONFIGURATION_ACTIVITY =
87            "android.service.zen.automatic.configurationActivity";
88
89    /**
90     * The name of the {@code meta-data} tag containing the maximum number of rule instances that
91     * can be created for this rule type. Omit or enter a value <= 0 to allow unlimited instances.
92     */
93    public static final String META_DATA_RULE_INSTANCE_LIMIT =
94            "android.service.zen.automatic.ruleInstanceLimit";
95
96    /**
97     * A String rule id extra passed to {@link #META_DATA_CONFIGURATION_ACTIVITY}.
98     */
99    public static final String EXTRA_RULE_ID = "android.service.notification.extra.RULE_ID";
100
101    /**
102     * Called when this service is connected.
103     */
104    abstract public void onConnected();
105
106    @SystemApi
107    public void onRequestConditions(int relevance) {}
108
109    /**
110     * Called by the system when there is a new {@link Condition} to be managed by this provider.
111     * @param conditionId the Uri describing the criteria of the condition.
112     */
113    abstract public void onSubscribe(Uri conditionId);
114
115    /**
116     * Called by the system when a {@link Condition} has been deleted.
117     * @param conditionId the Uri describing the criteria of the deleted condition.
118     */
119    abstract public void onUnsubscribe(Uri conditionId);
120
121    private final INotificationManager getNotificationInterface() {
122        if (mNoMan == null) {
123            mNoMan = INotificationManager.Stub.asInterface(
124                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
125        }
126        return mNoMan;
127    }
128
129    /**
130     * Request that the provider be rebound, after a previous call to (@link requestUnbind).
131     *
132     * <p>This method will fail for providers that have not been granted the permission by the user.
133     */
134    public static final void requestRebind(ComponentName componentName) {
135        INotificationManager noMan = INotificationManager.Stub.asInterface(
136                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
137        try {
138            noMan.requestBindProvider(componentName);
139        } catch (RemoteException ex) {
140            throw ex.rethrowFromSystemServer();
141        }
142    }
143
144    /**
145     * Request that the provider service be unbound.
146     *
147     * <p>This will no longer receive subscription updates and will not be able to update the
148     * state of conditions until {@link #requestRebind(ComponentName)} is called.
149     * The service will likely be killed by the system after this call.
150     *
151     * <p>The service should wait for the {@link #onConnected()} event before performing this
152     * operation.
153     */
154    public final void requestUnbind() {
155        INotificationManager noMan = getNotificationInterface();
156        try {
157            noMan.requestUnbindProvider(mProvider);
158            // Disable future messages.
159            mProvider = null;
160        } catch (RemoteException ex) {
161            throw ex.rethrowFromSystemServer();
162        }
163    }
164
165    /**
166     * Informs the notification manager that the state of a Condition has changed. Use this method
167     * to put the system into Do Not Disturb mode or request that it exits Do Not Disturb mode. This
168     * call will be ignored unless there is an enabled {@link android.app.AutomaticZenRule} owned by
169     * service that has an {@link android.app.AutomaticZenRule#getConditionId()} equal to this
170     * {@link Condition#id}.
171     * @param condition the condition that has changed.
172     */
173    public final void notifyCondition(Condition condition) {
174        if (condition == null) return;
175        notifyConditions(new Condition[]{ condition });
176    }
177
178    /**
179     * Informs the notification manager that the state of one or more Conditions has changed. See
180     * {@link #notifyCondition(Condition)} for restrictions.
181     * @param conditions the changed conditions.
182     */
183    public final void notifyConditions(Condition... conditions) {
184        if (!isBound() || conditions == null) return;
185        try {
186            getNotificationInterface().notifyConditions(getPackageName(), mProvider, conditions);
187        } catch (android.os.RemoteException ex) {
188            Log.v(TAG, "Unable to contact notification manager", ex);
189        }
190    }
191
192    @Override
193    public IBinder onBind(Intent intent) {
194        if (mProvider == null) {
195            mProvider = new Provider();
196        }
197        return mProvider;
198    }
199
200    private boolean isBound() {
201        if (mProvider == null) {
202            Log.w(TAG, "Condition provider service not yet bound.");
203            return false;
204        }
205        return true;
206    }
207
208    private final class Provider extends IConditionProvider.Stub {
209        @Override
210        public void onConnected() {
211            mHandler.obtainMessage(H.ON_CONNECTED).sendToTarget();
212        }
213
214        @Override
215        public void onSubscribe(Uri conditionId) {
216            mHandler.obtainMessage(H.ON_SUBSCRIBE, conditionId).sendToTarget();
217        }
218
219        @Override
220        public void onUnsubscribe(Uri conditionId) {
221            mHandler.obtainMessage(H.ON_UNSUBSCRIBE, conditionId).sendToTarget();
222        }
223    }
224
225    private final class H extends Handler {
226        private static final int ON_CONNECTED = 1;
227        private static final int ON_SUBSCRIBE = 3;
228        private static final int ON_UNSUBSCRIBE = 4;
229
230        @Override
231        public void handleMessage(Message msg) {
232            String name = null;
233            if (!isBound()) {
234                return;
235            }
236            try {
237                switch(msg.what) {
238                    case ON_CONNECTED:
239                        name = "onConnected";
240                        onConnected();
241                        break;
242                    case ON_SUBSCRIBE:
243                        name = "onSubscribe";
244                        onSubscribe((Uri)msg.obj);
245                        break;
246                    case ON_UNSUBSCRIBE:
247                        name = "onUnsubscribe";
248                        onUnsubscribe((Uri)msg.obj);
249                        break;
250                }
251            } catch (Throwable t) {
252                Log.w(TAG, "Error running " + name, t);
253            }
254        }
255    }
256}
257