SettingInjectorService.java revision 7f6f45723adea684529dd9b7465d798f10c3acbf
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.IntentService; 20import android.content.Intent; 21import android.os.Bundle; 22import android.os.Message; 23import android.os.Messenger; 24import android.os.RemoteException; 25import android.preference.Preference; 26import android.util.Log; 27 28/** 29 * Dynamically specifies the summary (subtile) and enabled status of a preference injected into 30 * the "Settings > Location > Location services" list. 31 * 32 * The location services list is intended for use only by preferences that affect multiple apps from 33 * the same developer. Location settings that apply only to one app should be shown within that app, 34 * rather than in the system settings. 35 * 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 * <service android:name="com.example.android.injector.MyInjectorService" > 41 * <intent-filter> 42 * <action android:name="com.android.settings.InjectedLocationSetting" /> 43 * </intent-filter> 44 * 45 * <meta-data 46 * android:name="com.android.settings.InjectedLocationSetting" 47 * android:resource="@xml/my_injected_location_setting" /> 48 * </service> 49 * </pre> 50 * The resource file specifies the static data for the setting: 51 * <pre> 52 * <injected-location-setting xmlns:android="http://schemas.android.com/apk/res/android" 53 * android:label="@string/injected_setting_label" 54 * android:icon="@drawable/ic_launcher" 55 * android:settingsActivity="com.example.android.injector.MySettingActivity" 56 * /> 57 * </pre> 58 * Here: 59 * <ul> 60 * <li>label: The {@link Preference#getTitle()} value. The title should make it clear which apps 61 * are affected by the setting, typically by including the name of the developer. For example, 62 * "Acme Corp. ads preferences." </li> 63 * 64 * <li>icon: The {@link Preference#getIcon()} value. Typically this will be a generic icon for 65 * 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 #onHandleIntent(Intent)} must complete within 74 * 200 msec even if your app is not already running. This means that both 75 * {@link android.app.Application#onCreate()} and {@link #getStatus()} must be fast. If you exceed 76 * this time, then this can delay the retrieval of settings status for other apps as well. 77 * 78 * For consistency, the label and {@link #getStatus()} values should be provided in all of the 79 * locales supported by the system settings app. The text should not contain offensive language. 80 * 81 * For compactness, only one copy of a given setting should be injected. If each account has a 82 * distinct value for the setting, then the {@link #getStatus()} value should represent a summary of 83 * the state across all of the accounts and {@code settingsActivity} should display the value for 84 * each account. 85 * 86 * Apps that violate these guidelines will be taken down from the Google Play Store and/or flagged 87 * as malware. 88 */ 89// TODO: is there a public list of supported locales? 90// TODO: is there a public list of guidelines for settings text? 91// TODO: would a bound service be better? E.g., we could just disconnect if a service took too long 92public abstract class SettingInjectorService extends IntentService { 93 94 private static final String TAG = "SettingInjectorService"; 95 96 /** 97 * Name of the bundle key for the string specifying the summary for the setting (e.g., "ON" or 98 * "OFF"). 99 * 100 * @hide 101 */ 102 public static final String SUMMARY_KEY = "summary"; 103 104 /** 105 * Name of the bundle key for the string specifying whether the setting is currently enabled. 106 * 107 * @hide 108 */ 109 public static final String ENABLED_KEY = "enabled"; 110 111 /** 112 * Name of the intent key used to specify the messenger 113 * 114 * @hide 115 */ 116 public static final String MESSENGER_KEY = "messenger"; 117 118 /** 119 * Intent action a client should broadcast when the value of one of its injected settings has 120 * changed, so that the setting can be updated in the UI. 121 */ 122 public static final String ACTION_INJECTED_SETTING_CHANGED = 123 "com.android.location.InjectedSettingChanged"; 124 125 private final String mName; 126 127 /** 128 * Constructor. 129 * 130 * @param name used to name the worker thread and in log messages 131 */ 132 public SettingInjectorService(String name) { 133 super(name); 134 mName = name; 135 } 136 137 @Override 138 final protected void onHandleIntent(Intent intent) { 139 // Get messenger first to ensure intent doesn't get messed with (in case we later decide 140 // to pass intent into getStatus()) 141 Messenger messenger = intent.getParcelableExtra(MESSENGER_KEY); 142 143 Status status; 144 try { 145 status = getStatus(); 146 } catch (RuntimeException e) { 147 Log.e(TAG, mName + ": error getting status", e); 148 status = null; 149 } 150 151 // Send the status back to the caller via the messenger 152 Message message = Message.obtain(); 153 Bundle bundle = new Bundle(); 154 if (status != null) { 155 bundle.putString(SUMMARY_KEY, status.summary); 156 bundle.putBoolean(ENABLED_KEY, status.enabled); 157 } 158 message.setData(bundle); 159 160 if (Log.isLoggable(TAG, Log.DEBUG)) { 161 Log.d(TAG, mName + ": received " + intent + " and " + status 162 + ", sending message: " + message); 163 } 164 try { 165 messenger.send(message); 166 } catch (RemoteException e) { 167 Log.e(TAG, mName + ": sending status failed", e); 168 } 169 } 170 171 /** 172 * Reads the status of the setting. 173 */ 174 protected abstract Status getStatus(); 175 176 /** 177 * Dynamic characteristics of an injected location setting. 178 */ 179 public static final class Status { 180 181 /** 182 * The {@link Preference#getSummary()} value 183 */ 184 public final String summary; 185 186 /** 187 * The {@link Preference#isEnabled()} value 188 */ 189 public final boolean enabled; 190 191 /** 192 * Constructor. 193 * <p/> 194 * Note that to prevent churn in the settings list, there is no support for dynamically 195 * choosing to hide a setting. Instead you should provide a {@code enabled} value of false, 196 * which will gray the setting out and disable the link from "Settings > Location" 197 * to your setting activity. One reason why you might choose to do this is if 198 * {@link android.provider.Settings.Secure#LOCATION_MODE} 199 * is {@link android.provider.Settings.Secure#LOCATION_MODE_OFF}. 200 * 201 * It is possible that the user may click on the setting before you return a false value for 202 * {@code enabled}, so your settings activity must handle the case where it is invoked even 203 * though the setting is disabled. The simplest approach may be to simply call 204 * {@link android.app.Activity#finish()} when disabled. 205 * 206 * @param summary the {@link Preference#getSummary()} value (allowed to be null or empty) 207 * @param enabled the {@link Preference#isEnabled()} value 208 */ 209 public Status(String summary, boolean enabled) { 210 this.summary = summary; 211 this.enabled = enabled; 212 } 213 214 @Override 215 public String toString() { 216 return "Status{summary='" + summary + '\'' + ", enabled=" + enabled + '}'; 217 } 218 } 219} 220