/*
* 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.app.Service;
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 enabled status of a preference injected into
* the list of app settings displayed by the system settings app
*
* 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.
*
* To add a preference to the list, a subclass of {@link SettingInjectorService} must be declared in
* the manifest as so:
*
*
* <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>
*
* The resource file specifies the static data for the setting:
*
* <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"
* />
*
* Here:
*
* - 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."
*
* - 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.
*
* - 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.
*
*
* To ensure a good user experience, your {@link android.app.Application#onCreate()},
* and {@link #onGetEnabled()} methods must all be fast. If either is slow,
* it can delay the display of settings values for other apps as well. Note further that these
* methods are called on your app's UI thread.
*
* For compactness, only one copy of a given setting should be injected. If each account has a
* distinct value for the setting, then only {@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 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) {
boolean enabled;
try {
enabled = onGetEnabled();
} catch (RuntimeException e) {
// Exception. Send status anyway, so that settings injector can immediately start
// loading the status of the next setting.
sendStatus(intent, true);
throw e;
}
sendStatus(intent, enabled);
}
/**
* Send the enabled values back to the caller via the messenger encoded in the
* intent.
*/
private void sendStatus(Intent intent, boolean enabled) {
Message message = Message.obtain();
Bundle bundle = new Bundle();
bundle.putBoolean(ENABLED_KEY, enabled);
message.setData(bundle);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, mName + ": received " + intent
+ ", enabled=" + enabled + ", sending message: " + message);
}
Messenger messenger = intent.getParcelableExtra(MESSENGER_KEY);
try {
messenger.send(message);
} catch (RemoteException e) {
Log.e(TAG, mName + ": sending dynamic status failed", e);
}
}
/**
* This method is no longer called, because status values are no longer shown for any injected
* setting.
*
* @return ignored
*
* @deprecated not called any more
*/
@Deprecated
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.
*
* 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}.
*
* 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();
}