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