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