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 
17 package android.service.notification;
18 
19 import android.annotation.SdkConstant;
20 import android.annotation.SystemApi;
21 import android.app.INotificationManager;
22 import android.app.Service;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.net.Uri;
27 import android.os.Handler;
28 import android.os.IBinder;
29 import android.os.Message;
30 import android.os.RemoteException;
31 import android.os.ServiceManager;
32 import 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  */
60 public 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      */
onConnected()104     abstract public void onConnected();
105 
106     @SystemApi
onRequestConditions(int relevance)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      */
onSubscribe(Uri conditionId)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      */
onUnsubscribe(Uri conditionId)119     abstract public void onUnsubscribe(Uri conditionId);
120 
getNotificationInterface()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      */
requestRebind(ComponentName componentName)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      */
requestUnbind()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      */
notifyCondition(Condition condition)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      */
notifyConditions(Condition... conditions)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
onBind(Intent intent)193     public IBinder onBind(Intent intent) {
194         if (mProvider == null) {
195             mProvider = new Provider();
196         }
197         return mProvider;
198     }
199 
isBound()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
onConnected()210         public void onConnected() {
211             mHandler.obtainMessage(H.ON_CONNECTED).sendToTarget();
212         }
213 
214         @Override
onSubscribe(Uri conditionId)215         public void onSubscribe(Uri conditionId) {
216             mHandler.obtainMessage(H.ON_SUBSCRIBE, conditionId).sendToTarget();
217         }
218 
219         @Override
onUnsubscribe(Uri conditionId)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
handleMessage(Message msg)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