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 * <service android:name=".MyConditionProvider" 44 * android:label="@string/service_name" 45 * android:permission="android.permission.BIND_CONDITION_PROVIDER_SERVICE"> 46 * <intent-filter> 47 * <action android:name="android.service.notification.ConditionProviderService" /> 48 * </intent-filter> 49 * <meta-data 50 * android:name="android.service.zen.automatic.ruleType" 51 * android:value="@string/my_condition_rule"> 52 * </meta-data> 53 * <meta-data 54 * android:name="android.service.zen.automatic.configurationActivity" 55 * android:value="com.my.package/.MyConditionConfigurationActivity"> 56 * </meta-data> 57 * </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