| /* | |
| * Copyright (C) 2013 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| package android.location; | |
| import android.annotation.NonNull; | |
| import android.app.Service; | |
| import android.content.Context; | |
| import android.content.Intent; | |
| import android.os.Bundle; | |
| import android.os.IBinder; | |
| import android.os.Message; | |
| import android.os.Messenger; | |
| import android.os.RemoteException; | |
| import android.util.Log; | |
| /** | |
| * Dynamically specifies the summary (subtitle) and enabled status of a preference injected into | |
| * the list of app settings displayed by the system settings app | |
| * <p/> | |
| * For use only by apps that are included in the system image, for preferences that affect multiple | |
| * apps. Location settings that apply only to one app should be shown within that app, | |
| * rather than in the system settings. | |
| * <p/> | |
| * To add a preference to the list, a subclass of {@link SettingInjectorService} must be declared in | |
| * the manifest as so: | |
| * | |
| * <pre> | |
| * <service android:name="com.example.android.injector.MyInjectorService" > | |
| * <intent-filter> | |
| * <action android:name="android.location.SettingInjectorService" /> | |
| * </intent-filter> | |
| * | |
| * <meta-data | |
| * android:name="android.location.SettingInjectorService" | |
| * android:resource="@xml/my_injected_location_setting" /> | |
| * </service> | |
| * </pre> | |
| * The resource file specifies the static data for the setting: | |
| * <pre> | |
| * <injected-location-setting xmlns:android="http://schemas.android.com/apk/res/android" | |
| * android:title="@string/injected_setting_title" | |
| * android:icon="@drawable/ic_acme_corp" | |
| * android:settingsActivity="com.example.android.injector.MySettingActivity" | |
| * /> | |
| * </pre> | |
| * Here: | |
| * <ul> | |
| * <li>title: The {@link android.preference.Preference#getTitle()} value. The title should make | |
| * it clear which apps are affected by the setting, typically by including the name of the | |
| * developer. For example, "Acme Corp. ads preferences." </li> | |
| * | |
| * <li>icon: The {@link android.preference.Preference#getIcon()} value. Typically this will be a | |
| * generic icon for the developer rather than the icon for an individual app.</li> | |
| * | |
| * <li>settingsActivity: the activity which is launched to allow the user to modify the setting | |
| * value. The activity must be in the same package as the subclass of | |
| * {@link SettingInjectorService}. The activity should use your own branding to help emphasize | |
| * to the user that it is not part of the system settings.</li> | |
| * </ul> | |
| * | |
| * To ensure a good user experience, your {@link android.app.Application#onCreate()}, | |
| * {@link #onGetSummary()}, and {@link #onGetEnabled()} methods must all be fast. If any are slow, | |
| * it can delay the display of settings values for other apps as well. Note further that all are | |
| * called on your app's UI thread. | |
| * <p/> | |
| * For compactness, only one copy of a given setting should be injected. If each account has a | |
| * distinct value for the setting, then the {@link #onGetSummary()} value should represent a summary | |
| * of the state across all of the accounts and {@code settingsActivity} should display the value for | |
| * each account. | |
| */ | |
| public abstract class SettingInjectorService extends Service { | |
| private static final String TAG = "SettingInjectorService"; | |
| /** | |
| * Intent action that must be declared in the manifest for the subclass. Used to start the | |
| * service to read the dynamic status for the setting. | |
| */ | |
| public static final String ACTION_SERVICE_INTENT = "android.location.SettingInjectorService"; | |
| /** | |
| * Name of the meta-data tag used to specify the resource file that includes the settings | |
| * attributes. | |
| */ | |
| public static final String META_DATA_NAME = "android.location.SettingInjectorService"; | |
| /** | |
| * Name of the XML tag that includes the attributes for the setting. | |
| */ | |
| public static final String ATTRIBUTES_NAME = "injected-location-setting"; | |
| /** | |
| * Intent action a client should broadcast when the value of one of its injected settings has | |
| * changed, so that the setting can be updated in the UI. | |
| */ | |
| public static final String ACTION_INJECTED_SETTING_CHANGED = | |
| "android.location.InjectedSettingChanged"; | |
| /** | |
| * Name of the bundle key for the string specifying the summary for the setting (e.g., "ON" or | |
| * "OFF"). | |
| * | |
| * @hide | |
| */ | |
| public static final String SUMMARY_KEY = "summary"; | |
| /** | |
| * Name of the bundle key for the string specifying whether the setting is currently enabled. | |
| * | |
| * @hide | |
| */ | |
| public static final String ENABLED_KEY = "enabled"; | |
| /** | |
| * Name of the intent key used to specify the messenger | |
| * | |
| * @hide | |
| */ | |
| public static final String MESSENGER_KEY = "messenger"; | |
| private final String mName; | |
| /** | |
| * Constructor. | |
| * | |
| * @param name used to identify your subclass in log messages | |
| */ | |
| public SettingInjectorService(String name) { | |
| mName = name; | |
| } | |
| @Override | |
| public final IBinder onBind(Intent intent) { | |
| return null; | |
| } | |
| @Override | |
| public final void onStart(Intent intent, int startId) { | |
| super.onStart(intent, startId); | |
| } | |
| @Override | |
| public final int onStartCommand(Intent intent, int flags, int startId) { | |
| onHandleIntent(intent); | |
| stopSelf(startId); | |
| return START_NOT_STICKY; | |
| } | |
| private void onHandleIntent(Intent intent) { | |
| String summary = null; | |
| boolean enabled = false; | |
| try { | |
| summary = onGetSummary(); | |
| enabled = onGetEnabled(); | |
| } finally { | |
| // If exception happens, send status anyway, so that settings injector can immediately | |
| // start loading the status of the next setting. But leave the exception uncaught to | |
| // crash the injector service itself. | |
| sendStatus(intent, summary, enabled); | |
| } | |
| } | |
| /** | |
| * Send the enabled values back to the caller via the messenger encoded in the | |
| * intent. | |
| */ | |
| private void sendStatus(Intent intent, String summary, boolean enabled) { | |
| Messenger messenger = intent.getParcelableExtra(MESSENGER_KEY); | |
| // Bail out to avoid crashing GmsCore with incoming malicious Intent. | |
| if (messenger == null) { | |
| return; | |
| } | |
| Message message = Message.obtain(); | |
| Bundle bundle = new Bundle(); | |
| bundle.putString(SUMMARY_KEY, summary); | |
| bundle.putBoolean(ENABLED_KEY, enabled); | |
| message.setData(bundle); | |
| if (Log.isLoggable(TAG, Log.DEBUG)) { | |
| Log.d(TAG, mName + ": received " + intent + ", summary=" + summary | |
| + ", enabled=" + enabled + ", sending message: " + message); | |
| } | |
| try { | |
| messenger.send(message); | |
| } catch (RemoteException e) { | |
| Log.e(TAG, mName + ": sending dynamic status failed", e); | |
| } | |
| } | |
| /** | |
| * Returns the {@link android.preference.Preference#getSummary()} value (allowed to be null or | |
| * empty). Should not perform unpredictably-long operations such as network access--see the | |
| * running-time comments in the class-level javadoc. | |
| * <p/> | |
| * This method is called on KitKat, and Q+ devices. | |
| * | |
| * @return the {@link android.preference.Preference#getSummary()} value | |
| */ | |
| protected abstract String onGetSummary(); | |
| /** | |
| * Returns the {@link android.preference.Preference#isEnabled()} value. Should not perform | |
| * unpredictably-long operations such as network access--see the running-time comments in the | |
| * class-level javadoc. | |
| * <p/> | |
| * Note that to prevent churn in the settings list, there is no support for dynamically choosing | |
| * to hide a setting. Instead you should have this method return false, which will disable the | |
| * setting and its link to your setting activity. One reason why you might choose to do this is | |
| * if {@link android.provider.Settings.Secure#LOCATION_MODE} is {@link | |
| * android.provider.Settings.Secure#LOCATION_MODE_OFF}. | |
| * <p/> | |
| * It is possible that the user may click on the setting before this method returns, so your | |
| * settings activity must handle the case where it is invoked even though the setting is | |
| * disabled. The simplest approach may be to simply call {@link android.app.Activity#finish()} | |
| * when disabled. | |
| * | |
| * @return the {@link android.preference.Preference#isEnabled()} value | |
| */ | |
| protected abstract boolean onGetEnabled(); | |
| /** | |
| * Sends a broadcast to refresh the injected settings on location settings page. | |
| */ | |
| public static final void refreshSettings(@NonNull Context context) { | |
| Intent intent = new Intent(ACTION_INJECTED_SETTING_CHANGED); | |
| context.sendBroadcast(intent); | |
| } | |
| } |