SettingInjectorService.java revision 17c5e79496bc1e2d53bc3c4e33bad4b39b80c36d
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
17package android.location;
18
19import android.app.Service;
20import android.content.Intent;
21import android.os.Bundle;
22import android.os.IBinder;
23import android.os.Message;
24import android.os.Messenger;
25import android.os.RemoteException;
26import 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 */
82public 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