ConditionProviderService.java revision b0a773f6b338f8ec35d9faaa3b566f482bdc2f92
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.app.INotificationManager;
21import android.app.Service;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.net.Uri;
26import android.os.Handler;
27import android.os.IBinder;
28import android.os.Message;
29import android.os.ServiceManager;
30import android.util.Log;
31
32/**
33 * A service that provides conditions about boolean state.
34 * <p>To extend this class, you must declare the service in your manifest file with
35 * the {@link android.Manifest.permission#BIND_CONDITION_PROVIDER_SERVICE} permission
36 * and include an intent filter with the {@link #SERVICE_INTERFACE} action. If you want users to be
37 * able to create and update conditions for this service to monitor, include the
38 * {@link #META_DATA_RULE_TYPE}, {@link #META_DATA_DEFAULT_CONDITION_ID}, and
39 * {@link #META_DATA_CONFIGURATION_ACTIVITY} tags. For example:</p>
40 * <pre>
41 * &lt;service android:name=".MyConditionProvider"
42 *          android:label="&#64;string/service_name"
43 *          android:permission="android.permission.BIND_CONDITION_PROVIDER_SERVICE">
44 *     &lt;intent-filter>
45 *         &lt;action android:name="android.service.notification.ConditionProviderService" />
46 *     &lt;/intent-filter>
47 *     &lt;meta-data
48 *               android:name="android.service.zen.automatic.ruleType"
49 *               android:value="@string/my_condition_rule">
50 *           &lt;/meta-data>
51 *           &lt;meta-data
52 *               android:name="android.service.zen.automatic.defaultConditionId"
53 *               android:value="condition://com.my.package/mycondition">
54 *           &lt;/meta-data>
55 *           &lt;meta-data
56 *               android:name="android.service.zen.automatic.configurationActivity"
57 *               android:value="com.my.package/.MyConditionConfigurationActivity">
58 *           &lt;/meta-data>
59 * &lt;/service></pre>
60 *
61 */
62public abstract class ConditionProviderService extends Service {
63    private final String TAG = ConditionProviderService.class.getSimpleName()
64            + "[" + getClass().getSimpleName() + "]";
65
66    private final H mHandler = new H();
67
68    private Provider mProvider;
69    private INotificationManager mNoMan;
70
71    /**
72     * The {@link Intent} that must be declared as handled by the service.
73     */
74    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
75    public static final String SERVICE_INTERFACE
76            = "android.service.notification.ConditionProviderService";
77
78    /**
79     * The name of the {@code meta-data} tag containing a localized name of the type of zen rules
80     * provided by this service.
81     */
82    public static final String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType";
83
84    /**
85     * The name of the {@code meta-data} tag containing a default Condition {@link Uri} that can
86     * be parsed by this service.
87     */
88    public static final String META_DATA_DEFAULT_CONDITION_ID =
89            "android.service.zen.automatic.defaultConditionId";
90
91    /**
92     * The name of the {@code meta-data} tag containing the {@link ComponentName} of an activity
93     * that allows users to configure the conditions provided by this service.
94     */
95    public static final String META_DATA_CONFIGURATION_ACTIVITY =
96            "android.service.zen.automatic.configurationActivity";
97
98    /**
99     * A condition {@link Uri} extra passed to {@link #META_DATA_CONFIGURATION_ACTIVITY}. If the
100     * condition Uri is modified by that activity, it must be included in the result Intent extras
101     * in order to be persisted.
102     */
103    public static final String EXTRA_CONDITION_ID = "android.content.automatic.conditionId";
104
105    /**
106     * A String rule name extra passed to {@link #META_DATA_CONFIGURATION_ACTIVITY}. This extra is
107     * informative only, and will be ignored if included in the result Intent extras.
108     */
109    public static final String EXTRA_RULE_NAME = "android.content.automatic.ruleName";
110
111    /**
112     * Called when this service is connected.
113     */
114    abstract public void onConnected();
115
116    /**
117     * Called when the system wants to know the state of Conditions managed by this provider.
118     *
119     * Implementations should evaluate the state of all subscribed conditions, and provide updates
120     * by calling {@link #notifyCondition(Condition)} or {@link #notifyConditions(Condition...)}.
121     * @param relevance
122     */
123    abstract public void onRequestConditions(int relevance);
124
125    /**
126     * Called by the system when there is a new {@link Condition} to be managed by this provider.
127     * @param conditionId the Uri describing the criteria of the condition.
128     */
129    abstract public void onSubscribe(Uri conditionId);
130
131    /**
132     * Called by the system when a {@link Condition} has been deleted.
133     * @param conditionId the Uri describing the criteria of the deleted condition.
134     */
135    abstract public void onUnsubscribe(Uri conditionId);
136
137    private final INotificationManager getNotificationInterface() {
138        if (mNoMan == null) {
139            mNoMan = INotificationManager.Stub.asInterface(
140                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
141        }
142        return mNoMan;
143    }
144
145    /**
146     * Informs the notification manager that the state of a Condition has changed.
147     * @param condition the condition that has changed.
148     */
149    public final void notifyCondition(Condition condition) {
150        if (condition == null) return;
151        notifyConditions(new Condition[]{ condition });
152    }
153
154    /**
155     * Informs the notification manager that the state of one or more Conditions has changed.
156     * @param conditions the changed conditions.
157     */
158    public final void notifyConditions(Condition... conditions) {
159        if (!isBound() || conditions == null) return;
160        try {
161            getNotificationInterface().notifyConditions(getPackageName(), mProvider, conditions);
162        } catch (android.os.RemoteException ex) {
163            Log.v(TAG, "Unable to contact notification manager", ex);
164        }
165    }
166
167    @Override
168    public IBinder onBind(Intent intent) {
169        if (mProvider == null) {
170            mProvider = new Provider();
171        }
172        return mProvider;
173    }
174
175    private boolean isBound() {
176        if (mProvider == null) {
177            Log.w(TAG, "Condition provider service not yet bound.");
178            return false;
179        }
180        return true;
181    }
182
183    private final class Provider extends IConditionProvider.Stub {
184        @Override
185        public void onConnected() {
186            mHandler.obtainMessage(H.ON_CONNECTED).sendToTarget();
187        }
188
189        @Override
190        public void onRequestConditions(int relevance) {
191            mHandler.obtainMessage(H.ON_REQUEST_CONDITIONS, relevance, 0).sendToTarget();
192        }
193
194        @Override
195        public void onSubscribe(Uri conditionId) {
196            mHandler.obtainMessage(H.ON_SUBSCRIBE, conditionId).sendToTarget();
197        }
198
199        @Override
200        public void onUnsubscribe(Uri conditionId) {
201            mHandler.obtainMessage(H.ON_UNSUBSCRIBE, conditionId).sendToTarget();
202        }
203    }
204
205    private final class H extends Handler {
206        private static final int ON_CONNECTED = 1;
207        private static final int ON_REQUEST_CONDITIONS = 2;
208        private static final int ON_SUBSCRIBE = 3;
209        private static final int ON_UNSUBSCRIBE = 4;
210
211        @Override
212        public void handleMessage(Message msg) {
213            String name = null;
214            try {
215                switch(msg.what) {
216                    case ON_CONNECTED:
217                        name = "onConnected";
218                        onConnected();
219                        break;
220                    case ON_REQUEST_CONDITIONS:
221                        name = "onRequestConditions";
222                        onRequestConditions(msg.arg1);
223                        break;
224                    case ON_SUBSCRIBE:
225                        name = "onSubscribe";
226                        onSubscribe((Uri)msg.obj);
227                        break;
228                    case ON_UNSUBSCRIBE:
229                        name = "onUnsubscribe";
230                        onUnsubscribe((Uri)msg.obj);
231                        break;
232                }
233            } catch (Throwable t) {
234                Log.w(TAG, "Error running " + name, t);
235            }
236        }
237    }
238}
239