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 * <service android:name="com.example.android.injector.MyInjectorService" > 41 * <intent-filter> 42 * <action android:name="android.location.SettingInjectorService" /> 43 * </intent-filter> 44 * 45 * <meta-data 46 * android:name="android.location.SettingInjectorService" 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:title="@string/injected_setting_title" 54 * android:icon="@drawable/ic_acme_corp" 55 * android:settingsActivity="com.example.android.injector.MySettingActivity" 56 * /> 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