• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
SettingInjectorService(String name)131     public SettingInjectorService(String name) {
132         mName = name;
133     }
134 
135     @Override
onBind(Intent intent)136     public final IBinder onBind(Intent intent) {
137         return null;
138     }
139 
140     @Override
onStart(Intent intent, int startId)141     public final void onStart(Intent intent, int startId) {
142         super.onStart(intent, startId);
143     }
144 
145     @Override
onStartCommand(Intent intent, int flags, int startId)146     public final int onStartCommand(Intent intent, int flags, int startId) {
147         onHandleIntent(intent);
148         stopSelf(startId);
149         return START_NOT_STICKY;
150     }
151 
onHandleIntent(Intent intent)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      */
sendStatus(Intent intent, boolean enabled)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
onGetSummary()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      */
onGetEnabled()219     protected abstract boolean onGetEnabled();
220 }
221