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.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      */
    104     abstract public void onConnected();
    105 
    106     @SystemApi
    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      */
    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      */
    119     abstract public void onUnsubscribe(Uri conditionId);
    120 
    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      */
    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      */
    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      */
    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      */
    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
    193     public IBinder onBind(Intent intent) {
    194         if (mProvider == null) {
    195             mProvider = new Provider();
    196         }
    197         return mProvider;
    198     }
    199 
    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
    210         public void onConnected() {
    211             mHandler.obtainMessage(H.ON_CONNECTED).sendToTarget();
    212         }
    213 
    214         @Override
    215         public void onSubscribe(Uri conditionId) {
    216             mHandler.obtainMessage(H.ON_SUBSCRIBE, conditionId).sendToTarget();
    217         }
    218 
    219         @Override
    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
    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