Home | History | Annotate | Download | only in notification
      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      */
    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