Home | History | Annotate | Download | only in location
      1 /*
      2  * Copyright (C) 2013 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.location;
     18 
     19 import android.app.Service;
     20 import android.content.Intent;
     21 import android.os.Bundle;
     22 import android.os.IBinder;
     23 import android.os.Message;
     24 import android.os.Messenger;
     25 import android.os.RemoteException;
     26 import android.util.Log;
     27 
     28 /**
     29  * Dynamically specifies the enabled status of a preference injected into
     30  * the list of app settings displayed by the system settings app
     31  * <p/>
     32  * For use only by apps that are included in the system image, for preferences that affect multiple
     33  * apps. Location settings that apply only to one app should be shown within that app,
     34  * rather than in the system settings.
     35  * <p/>
     36  * To add a preference to the list, a subclass of {@link SettingInjectorService} must be declared in
     37  * the manifest as so:
     38  *
     39  * <pre>
     40  *     &lt;service android:name="com.example.android.injector.MyInjectorService" &gt;
     41  *         &lt;intent-filter&gt;
     42  *             &lt;action android:name="android.location.SettingInjectorService" /&gt;
     43  *         &lt;/intent-filter&gt;
     44  *
     45  *         &lt;meta-data
     46  *             android:name="android.location.SettingInjectorService"
     47  *             android:resource="@xml/my_injected_location_setting" /&gt;
     48  *     &lt;/service&gt;
     49  * </pre>
     50  * The resource file specifies the static data for the setting:
     51  * <pre>
     52  *     &lt;injected-location-setting xmlns:android="http://schemas.android.com/apk/res/android"
     53  *         android:title="@string/injected_setting_title"
     54  *         android:icon="@drawable/ic_acme_corp"
     55  *         android:settingsActivity="com.example.android.injector.MySettingActivity"
     56  *     /&gt;
     57  * </pre>
     58  * Here:
     59  * <ul>
     60  *     <li>title: The {@link android.preference.Preference#getTitle()} value. The title should make
     61  *     it clear which apps are affected by the setting, typically by including the name of the
     62  *     developer. For example, "Acme Corp. ads preferences." </li>
     63  *
     64  *     <li>icon: The {@link android.preference.Preference#getIcon()} value. Typically this will be a
     65  *     generic icon for the developer rather than the icon for an individual app.</li>
     66  *
     67  *     <li>settingsActivity: the activity which is launched to allow the user to modify the setting
     68  *     value.  The activity must be in the same package as the subclass of
     69  *     {@link SettingInjectorService}. The activity should use your own branding to help emphasize
     70  *     to the user that it is not part of the system settings.</li>
     71  * </ul>
     72  *
     73  * To ensure a good user experience, your {@link android.app.Application#onCreate()},
     74  * and {@link #onGetEnabled()} methods must all be fast. If either is slow,
     75  * it can delay the display of settings values for other apps as well. Note further that these
     76  * methods are called on your app's UI thread.
     77  * <p/>
     78  * For compactness, only one copy of a given setting should be injected. If each account has a
     79  * distinct value for the setting, then only {@code settingsActivity} should display the value for
     80  * each account.
     81  */
     82 public abstract class SettingInjectorService extends Service {
     83 
     84     private static final String TAG = "SettingInjectorService";
     85 
     86     /**
     87      * Intent action that must be declared in the manifest for the subclass. Used to start the
     88      * service to read the dynamic status for the setting.
     89      */
     90     public static final String ACTION_SERVICE_INTENT = "android.location.SettingInjectorService";
     91 
     92     /**
     93      * Name of the meta-data tag used to specify the resource file that includes the settings
     94      * attributes.
     95      */
     96     public static final String META_DATA_NAME = "android.location.SettingInjectorService";
     97 
     98     /**
     99      * Name of the XML tag that includes the attributes for the setting.
    100      */
    101     public static final String ATTRIBUTES_NAME = "injected-location-setting";
    102 
    103     /**
    104      * Intent action a client should broadcast when the value of one of its injected settings has
    105      * changed, so that the setting can be updated in the UI.
    106      */
    107     public static final String ACTION_INJECTED_SETTING_CHANGED =
    108             "android.location.InjectedSettingChanged";
    109 
    110     /**
    111      * Name of the bundle key for the string specifying whether the setting is currently enabled.
    112      *
    113      * @hide
    114      */
    115     public static final String ENABLED_KEY = "enabled";
    116 
    117     /**
    118      * Name of the intent key used to specify the messenger
    119      *
    120      * @hide
    121      */
    122     public static final String MESSENGER_KEY = "messenger";
    123 
    124     private final String mName;
    125 
    126     /**
    127      * Constructor.
    128      *
    129      * @param name used to identify your subclass in log messages
    130      */
    131     public SettingInjectorService(String name) {
    132         mName = name;
    133     }
    134 
    135     @Override
    136     public final IBinder onBind(Intent intent) {
    137         return null;
    138     }
    139 
    140     @Override
    141     public final void onStart(Intent intent, int startId) {
    142         super.onStart(intent, startId);
    143     }
    144 
    145     @Override
    146     public final int onStartCommand(Intent intent, int flags, int startId) {
    147         onHandleIntent(intent);
    148         stopSelf(startId);
    149         return START_NOT_STICKY;
    150     }
    151 
    152     private void onHandleIntent(Intent intent) {
    153 
    154         boolean enabled;
    155         try {
    156             enabled = onGetEnabled();
    157         } catch (RuntimeException e) {
    158             // Exception. Send status anyway, so that settings injector can immediately start
    159             // loading the status of the next setting.
    160             sendStatus(intent, true);
    161             throw e;
    162         }
    163 
    164         sendStatus(intent, enabled);
    165     }
    166 
    167     /**
    168      * Send the enabled values back to the caller via the messenger encoded in the
    169      * intent.
    170      */
    171     private void sendStatus(Intent intent, boolean enabled) {
    172         Message message = Message.obtain();
    173         Bundle bundle = new Bundle();
    174         bundle.putBoolean(ENABLED_KEY, enabled);
    175         message.setData(bundle);
    176 
    177         if (Log.isLoggable(TAG, Log.DEBUG)) {
    178             Log.d(TAG, mName + ": received " + intent
    179                     + ", enabled=" + enabled + ", sending message: " + message);
    180         }
    181 
    182         Messenger messenger = intent.getParcelableExtra(MESSENGER_KEY);
    183         try {
    184             messenger.send(message);
    185         } catch (RemoteException e) {
    186             Log.e(TAG, mName + ": sending dynamic status failed", e);
    187         }
    188     }
    189 
    190     /**
    191      * This method is no longer called, because status values are no longer shown for any injected
    192      * setting.
    193      *
    194      * @return ignored
    195      *
    196      * @deprecated not called any more
    197      */
    198     @Deprecated
    199     protected abstract String onGetSummary();
    200 
    201     /**
    202      * Returns the {@link android.preference.Preference#isEnabled()} value. Should not perform
    203      * unpredictably-long operations such as network access--see the running-time comments in the
    204      * class-level javadoc.
    205      * <p/>
    206      * Note that to prevent churn in the settings list, there is no support for dynamically choosing
    207      * to hide a setting. Instead you should have this method return false, which will disable the
    208      * setting and its link to your setting activity. One reason why you might choose to do this is
    209      * if {@link android.provider.Settings.Secure#LOCATION_MODE} is {@link
    210      * android.provider.Settings.Secure#LOCATION_MODE_OFF}.
    211      * <p/>
    212      * It is possible that the user may click on the setting before this method returns, so your
    213      * settings activity must handle the case where it is invoked even though the setting is
    214      * disabled. The simplest approach may be to simply call {@link android.app.Activity#finish()}
    215      * when disabled.
    216      *
    217      * @return the {@link android.preference.Preference#isEnabled()} value
    218      */
    219     protected abstract boolean onGetEnabled();
    220 }
    221