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.annotation.TestApi;
22 import android.app.ActivityManager;
23 import android.app.INotificationManager;
24 import android.app.Service;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.net.Uri;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Message;
32 import android.os.RemoteException;
33 import android.os.ServiceManager;
34 import 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  */
64 public 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      */
onConnected()108     abstract public void onConnected();
109 
110     @SystemApi
onRequestConditions(int relevance)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      */
onSubscribe(Uri conditionId)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      */
onUnsubscribe(Uri conditionId)123     abstract public void onUnsubscribe(Uri conditionId);
124 
getNotificationInterface()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      */
requestRebind(ComponentName componentName)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      */
requestUnbind()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      */
notifyCondition(Condition condition)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      */
notifyConditions(Condition... conditions)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
onBind(Intent intent)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
isBound()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
onConnected()218         public void onConnected() {
219             mHandler.obtainMessage(H.ON_CONNECTED).sendToTarget();
220         }
221 
222         @Override
onSubscribe(Uri conditionId)223         public void onSubscribe(Uri conditionId) {
224             mHandler.obtainMessage(H.ON_SUBSCRIBE, conditionId).sendToTarget();
225         }
226 
227         @Override
onUnsubscribe(Uri conditionId)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
handleMessage(Message msg)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