/* * Copyright (C) 2007 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.preference; import android.annotation.SystemApi; import android.annotation.XmlRes; import android.app.Activity; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.XmlResourceParser; import android.os.Bundle; import android.util.Log; import java.util.ArrayList; import java.util.HashSet; import java.util.List; /** * Used to help create {@link Preference} hierarchies * from activities or XML. *
* In most cases, clients should use
* {@link PreferenceActivity#addPreferencesFromIntent} or
* {@link PreferenceActivity#addPreferencesFromResource(int)}.
*
* @see PreferenceActivity
*/
public class PreferenceManager {
private static final String TAG = "PreferenceManager";
/**
* The Activity meta-data key for its XML preference hierarchy.
*/
public static final String METADATA_KEY_PREFERENCES = "android.preference";
public static final String KEY_HAS_SET_DEFAULT_VALUES = "_has_set_default_values";
/**
* @see #getActivity()
*/
private Activity mActivity;
/**
* Fragment that owns this instance.
*/
private PreferenceFragment mFragment;
/**
* The context to use. This should always be set.
*
* @see #mActivity
*/
private Context mContext;
/**
* The counter for unique IDs.
*/
private long mNextId = 0;
/**
* The counter for unique request codes.
*/
private int mNextRequestCode;
/**
* Cached shared preferences.
*/
private SharedPreferences mSharedPreferences;
/**
* If in no-commit mode, the shared editor to give out (which will be
* committed when exiting no-commit mode).
*/
private SharedPreferences.Editor mEditor;
/**
* Blocks commits from happening on the shared editor. This is used when
* inflating the hierarchy. Do not set this directly, use {@link #setNoCommit(boolean)}
*/
private boolean mNoCommit;
/**
* The SharedPreferences name that will be used for all {@link Preference}s
* managed by this instance.
*/
private String mSharedPreferencesName;
/**
* The SharedPreferences mode that will be used for all {@link Preference}s
* managed by this instance.
*/
private int mSharedPreferencesMode;
private static final int STORAGE_DEFAULT = 0;
private static final int STORAGE_DEVICE_PROTECTED = 1;
private static final int STORAGE_CREDENTIAL_PROTECTED = 2;
private int mStorage = STORAGE_DEFAULT;
/**
* The {@link PreferenceScreen} at the root of the preference hierarchy.
*/
private PreferenceScreen mPreferenceScreen;
/**
* List of activity result listeners.
*/
private List
* The {@link PreferenceManager#PreferenceManager(Activity)}
* should be used ANY time a preference will be displayed, since some preference
* types need an Activity for managed queries.
*/
/*package*/ PreferenceManager(Context context) {
init(context);
}
private void init(Context context) {
mContext = context;
setSharedPreferencesName(getDefaultSharedPreferencesName(context));
}
/**
* Sets the owning preference fragment
*/
void setFragment(PreferenceFragment fragment) {
mFragment = fragment;
}
/**
* Returns the owning preference fragment, if any.
*/
PreferenceFragment getFragment() {
return mFragment;
}
/**
* Returns a list of {@link Activity} (indirectly) that match a given
* {@link Intent}.
*
* @param queryIntent The Intent to match.
* @return The list of {@link ResolveInfo} that point to the matched
* activities.
*/
private List
* If a preference hierarchy is given, the new preference hierarchies will
* be merged in.
*
* @param queryIntent The intent to match activities.
* @param rootPreferences Optional existing hierarchy to merge the new
* hierarchies into.
* @return The root hierarchy (if one was not provided, the new hierarchy's
* root).
*/
PreferenceScreen inflateFromIntent(Intent queryIntent, PreferenceScreen rootPreferences) {
final List
* On devices with direct boot, data stored in this location is encrypted
* with a key tied to the physical device, and it can be accessed
* immediately after the device has booted successfully, both
* before and after the user has authenticated with their
* credentials (such as a lock pattern or PIN).
*
* Because device-protected data is available without user authentication,
* you should carefully limit the data you store using this Context. For
* example, storing sensitive authentication tokens or passwords in the
* device-protected area is strongly discouraged.
*
* @see Context#createDeviceProtectedStorageContext()
*/
public void setStorageDeviceProtected() {
mStorage = STORAGE_DEVICE_PROTECTED;
mSharedPreferences = null;
}
/** @removed */
@Deprecated
public void setStorageDeviceEncrypted() {
setStorageDeviceProtected();
}
/**
* Explicitly set the storage location used internally by this class to be
* credential-protected storage. This is the default storage area for apps
* unless {@code forceDeviceProtectedStorage} was requested.
*
* On devices with direct boot, data stored in this location is encrypted
* with a key tied to user credentials, which can be accessed
* only after the user has entered their credentials (such as a
* lock pattern or PIN).
*
* @see Context#createCredentialProtectedStorageContext()
* @hide
*/
@SystemApi
public void setStorageCredentialProtected() {
mStorage = STORAGE_CREDENTIAL_PROTECTED;
mSharedPreferences = null;
}
/** @removed */
@Deprecated
public void setStorageCredentialEncrypted() {
setStorageCredentialProtected();
}
/**
* Indicates if the storage location used internally by this class is the
* default provided by the hosting {@link Context}.
*
* @see #setStorageDefault()
* @see #setStorageDeviceProtected()
*/
public boolean isStorageDefault() {
return mStorage == STORAGE_DEFAULT;
}
/**
* Indicates if the storage location used internally by this class is backed
* by device-protected storage.
*
* @see #setStorageDefault()
* @see #setStorageDeviceProtected()
*/
public boolean isStorageDeviceProtected() {
return mStorage == STORAGE_DEVICE_PROTECTED;
}
/**
* Indicates if the storage location used internally by this class is backed
* by credential-protected storage.
*
* @see #setStorageDefault()
* @see #setStorageDeviceProtected()
* @hide
*/
@SystemApi
public boolean isStorageCredentialProtected() {
return mStorage == STORAGE_CREDENTIAL_PROTECTED;
}
/**
* Gets a SharedPreferences instance that preferences managed by this will
* use.
*
* @return A SharedPreferences instance pointing to the file that contains
* the values of preferences that are managed by this.
*/
public SharedPreferences getSharedPreferences() {
if (mSharedPreferences == null) {
final Context storageContext;
switch (mStorage) {
case STORAGE_DEVICE_PROTECTED:
storageContext = mContext.createDeviceProtectedStorageContext();
break;
case STORAGE_CREDENTIAL_PROTECTED:
storageContext = mContext.createCredentialProtectedStorageContext();
break;
default:
storageContext = mContext;
break;
}
mSharedPreferences = storageContext.getSharedPreferences(mSharedPreferencesName,
mSharedPreferencesMode);
}
return mSharedPreferences;
}
/**
* Gets a SharedPreferences instance that points to the default file that is
* used by the preference framework in the given context.
*
* @param context The context of the preferences whose values are wanted.
* @return A SharedPreferences instance that can be used to retrieve and
* listen to values of the preferences.
*/
public static SharedPreferences getDefaultSharedPreferences(Context context) {
return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
getDefaultSharedPreferencesMode());
}
/**
* Returns the name used for storing default shared preferences.
*
* @see #getDefaultSharedPreferences(Context)
* @see Context#getSharedPreferencesPath(String)
*/
public static String getDefaultSharedPreferencesName(Context context) {
return context.getPackageName() + "_preferences";
}
private static int getDefaultSharedPreferencesMode() {
return Context.MODE_PRIVATE;
}
/**
* Returns the root of the preference hierarchy managed by this class.
*
* @return The {@link PreferenceScreen} object that is at the root of the hierarchy.
*/
PreferenceScreen getPreferenceScreen() {
return mPreferenceScreen;
}
/**
* Sets the root of the preference hierarchy.
*
* @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
* @return Whether the {@link PreferenceScreen} given is different than the previous.
*/
boolean setPreferences(PreferenceScreen preferenceScreen) {
if (preferenceScreen != mPreferenceScreen) {
mPreferenceScreen = preferenceScreen;
return true;
}
return false;
}
/**
* Finds a {@link Preference} based on its key.
*
* @param key The key of the preference to retrieve.
* @return The {@link Preference} with the key, or null.
* @see PreferenceGroup#findPreference(CharSequence)
*/
public Preference findPreference(CharSequence key) {
if (mPreferenceScreen == null) {
return null;
}
return mPreferenceScreen.findPreference(key);
}
/**
* Sets the default values from an XML preference file by reading the values defined
* by each {@link Preference} item's {@code android:defaultValue} attribute. This should
* be called by the application's main activity.
*
*
* @param context The context of the shared preferences.
* @param resId The resource ID of the preference XML file.
* @param readAgain Whether to re-read the default values.
* If false, this method sets the default values only if this
* method has never been called in the past (or if the
* {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared
* preferences file is false). To attempt to set the default values again
* bypassing this check, set {@code readAgain} to true.
*
* Note: this will NOT reset preferences back to their default
* values. For that functionality, use
* {@link PreferenceManager#getDefaultSharedPreferences(Context)}
* and clear it followed by a call to this method with this
* parameter set to true.
*/
public static void setDefaultValues(Context context, @XmlRes int resId, boolean readAgain) {
// Use the default shared preferences name and mode
setDefaultValues(context, getDefaultSharedPreferencesName(context),
getDefaultSharedPreferencesMode(), resId, readAgain);
}
/**
* Similar to {@link #setDefaultValues(Context, int, boolean)} but allows
* the client to provide the filename and mode of the shared preferences
* file.
*
* @param context The context of the shared preferences.
* @param sharedPreferencesName A custom name for the shared preferences file.
* @param sharedPreferencesMode The file creation mode for the shared preferences file, such
* as {@link android.content.Context#MODE_PRIVATE} or {@link
* android.content.Context#MODE_PRIVATE}
* @param resId The resource ID of the preference XML file.
* @param readAgain Whether to re-read the default values.
* If false, this method will set the default values only if this
* method has never been called in the past (or if the
* {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared
* preferences file is false). To attempt to set the default values again
* bypassing this check, set {@code readAgain} to true.
*
* Note: this will NOT reset preferences back to their default
* values. For that functionality, use
* {@link PreferenceManager#getDefaultSharedPreferences(Context)}
* and clear it followed by a call to this method with this
* parameter set to true.
*
* @see #setDefaultValues(Context, int, boolean)
* @see #setSharedPreferencesName(String)
* @see #setSharedPreferencesMode(int)
*/
public static void setDefaultValues(Context context, String sharedPreferencesName,
int sharedPreferencesMode, int resId, boolean readAgain) {
final SharedPreferences defaultValueSp = context.getSharedPreferences(
KEY_HAS_SET_DEFAULT_VALUES, Context.MODE_PRIVATE);
if (readAgain || !defaultValueSp.getBoolean(KEY_HAS_SET_DEFAULT_VALUES, false)) {
final PreferenceManager pm = new PreferenceManager(context);
pm.setSharedPreferencesName(sharedPreferencesName);
pm.setSharedPreferencesMode(sharedPreferencesMode);
pm.inflateFromResource(context, resId, null);
SharedPreferences.Editor editor =
defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true);
try {
editor.apply();
} catch (AbstractMethodError unused) {
// The app injected its own pre-Gingerbread
// SharedPreferences.Editor implementation without
// an apply method.
editor.commit();
}
}
}
/**
* Returns an editor to use when modifying the shared preferences.
*
* Do NOT commit unless {@link #shouldCommit()} returns true.
*
* @return An editor to use to write to shared preferences.
* @see #shouldCommit()
*/
SharedPreferences.Editor getEditor() {
if (mNoCommit) {
if (mEditor == null) {
mEditor = getSharedPreferences().edit();
}
return mEditor;
} else {
return getSharedPreferences().edit();
}
}
/**
* Whether it is the client's responsibility to commit on the
* {@link #getEditor()}. This will return false in cases where the writes
* should be batched, for example when inflating preferences from XML.
*
* @return Whether the client should commit.
*/
boolean shouldCommit() {
return !mNoCommit;
}
private void setNoCommit(boolean noCommit) {
if (!noCommit && mEditor != null) {
try {
mEditor.apply();
} catch (AbstractMethodError unused) {
// The app injected its own pre-Gingerbread
// SharedPreferences.Editor implementation without
// an apply method.
mEditor.commit();
}
}
mNoCommit = noCommit;
}
/**
* Returns the activity that shows the preferences. This is useful for doing
* managed queries, but in most cases the use of {@link #getContext()} is
* preferred.
*
* This will return null if this class was instantiated with a Context
* instead of Activity. For example, when setting the default values.
*
* @return The activity that shows the preferences.
* @see #mContext
*/
Activity getActivity() {
return mActivity;
}
/**
* Returns the context. This is preferred over {@link #getActivity()} when
* possible.
*
* @return The context.
*/
Context getContext() {
return mContext;
}
/**
* Registers a listener.
*
* @see OnActivityResultListener
*/
void registerOnActivityResultListener(OnActivityResultListener listener) {
synchronized (this) {
if (mActivityResultListeners == null) {
mActivityResultListeners = new ArrayList