Blame | Last modification | View Log | RSS feed
/*** Copyright 2010-present Facebook.** 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 com.facebook;import android.content.Context;import android.content.SharedPreferences;import android.os.Bundle;import android.util.Log;import com.facebook.internal.Logger;import com.facebook.internal.Utility;import com.facebook.internal.Validate;import org.json.JSONArray;import org.json.JSONException;import org.json.JSONObject;import java.util.ArrayList;import java.util.List;import java.util.Map;/** <p>* An implementation of {@link TokenCachingStrategy TokenCachingStrategy} that uses Android SharedPreferences* to persist information.* </p>* <p>* The data to be cached is passed in via a Bundle. Only non-null key-value-pairs where* the value is one of the following types (or an array of the same) are persisted:* boolean, byte, int, long, float, double, char. In addition, String and List<String>* are also supported.* </p>*/public class SharedPreferencesTokenCachingStrategy extends TokenCachingStrategy {private static final String DEFAULT_CACHE_KEY = "com.facebook.SharedPreferencesTokenCachingStrategy.DEFAULT_KEY";private static final String TAG = SharedPreferencesTokenCachingStrategy.class.getSimpleName();private static final String JSON_VALUE_TYPE = "valueType";private static final String JSON_VALUE = "value";private static final String JSON_VALUE_ENUM_TYPE = "enumType";private static final String TYPE_BOOLEAN = "bool";private static final String TYPE_BOOLEAN_ARRAY = "bool[]";private static final String TYPE_BYTE = "byte";private static final String TYPE_BYTE_ARRAY = "byte[]";private static final String TYPE_SHORT = "short";private static final String TYPE_SHORT_ARRAY = "short[]";private static final String TYPE_INTEGER = "int";private static final String TYPE_INTEGER_ARRAY = "int[]";private static final String TYPE_LONG = "long";private static final String TYPE_LONG_ARRAY = "long[]";private static final String TYPE_FLOAT = "float";private static final String TYPE_FLOAT_ARRAY = "float[]";private static final String TYPE_DOUBLE = "double";private static final String TYPE_DOUBLE_ARRAY = "double[]";private static final String TYPE_CHAR = "char";private static final String TYPE_CHAR_ARRAY = "char[]";private static final String TYPE_STRING = "string";private static final String TYPE_STRING_LIST = "stringList";private static final String TYPE_ENUM = "enum";private String cacheKey;private SharedPreferences cache;/*** Creates a default {@link SharedPreferencesTokenCachingStrategy SharedPreferencesTokenCachingStrategy}* instance that provides access to a single set of token information.** @param context* The Context object to use to get the SharedPreferences object.** @throws NullPointerException if the passed in Context is null*/public SharedPreferencesTokenCachingStrategy(Context context) {this(context, null);}/*** Creates a {@link SharedPreferencesTokenCachingStrategy SharedPreferencesTokenCachingStrategy} instance* that is distinct for the passed in cacheKey.** @param context* The Context object to use to get the SharedPreferences object.** @param cacheKey* Identifies a distinct set of token information.** @throws NullPointerException if the passed in Context is null*/public SharedPreferencesTokenCachingStrategy(Context context, String cacheKey) {Validate.notNull(context, "context");this.cacheKey = Utility.isNullOrEmpty(cacheKey) ? DEFAULT_CACHE_KEY : cacheKey;// If the application context is available, use that. However, if it isn't// available (possibly because of a context that was created manually), use// the passed in context directly.Context applicationContext = context.getApplicationContext();context = applicationContext != null ? applicationContext : context;this.cache = context.getSharedPreferences(this.cacheKey,Context.MODE_PRIVATE);}/*** Returns a Bundle that contains the information stored in this cache** @return A Bundle with the information contained in this cache*/public Bundle load() {Bundle settings = new Bundle();Map<String, ?> allCachedEntries = cache.getAll();for (String key : allCachedEntries.keySet()) {try {deserializeKey(key, settings);} catch (JSONException e) {// Error in the cache. So consider it corrupted and return nullLogger.log(LoggingBehavior.CACHE, Log.WARN, TAG,"Error reading cached value for key: '" + key + "' -- " + e);return null;}}return settings;}/*** Persists all supported data types present in the passed in Bundle, to the* cache** @param bundle* The Bundle containing information to be cached*/public void save(Bundle bundle) {Validate.notNull(bundle, "bundle");SharedPreferences.Editor editor = cache.edit();for (String key : bundle.keySet()) {try {serializeKey(key, bundle, editor);} catch (JSONException e) {// Error in the bundle. Don't store a partial cache.Logger.log(LoggingBehavior.CACHE, Log.WARN, TAG, "Error processing value for key: '" + key + "' -- " + e);// Bypass the commit and just return. This cancels the entire edit transactionreturn;}}editor.apply();}/*** Clears out all token information stored in this cache.*/public void clear() {cache.edit().clear().apply();}private void serializeKey(String key, Bundle bundle, SharedPreferences.Editor editor)throws JSONException {Object value = bundle.get(key);if (value == null) {// Cannot serialize null values.return;}String supportedType = null;JSONArray jsonArray = null;JSONObject json = new JSONObject();if (value instanceof Byte) {supportedType = TYPE_BYTE;json.put(JSON_VALUE, ((Byte)value).intValue());} else if (value instanceof Short) {supportedType = TYPE_SHORT;json.put(JSON_VALUE, ((Short)value).intValue());} else if (value instanceof Integer) {supportedType = TYPE_INTEGER;json.put(JSON_VALUE, ((Integer)value).intValue());} else if (value instanceof Long) {supportedType = TYPE_LONG;json.put(JSON_VALUE, ((Long)value).longValue());} else if (value instanceof Float) {supportedType = TYPE_FLOAT;json.put(JSON_VALUE, ((Float)value).doubleValue());} else if (value instanceof Double) {supportedType = TYPE_DOUBLE;json.put(JSON_VALUE, ((Double)value).doubleValue());} else if (value instanceof Boolean) {supportedType = TYPE_BOOLEAN;json.put(JSON_VALUE, ((Boolean)value).booleanValue());} else if (value instanceof Character) {supportedType = TYPE_CHAR;json.put(JSON_VALUE, value.toString());} else if (value instanceof String) {supportedType = TYPE_STRING;json.put(JSON_VALUE, (String)value);} else if (value instanceof Enum<?>) {supportedType = TYPE_ENUM;json.put(JSON_VALUE, value.toString());json.put(JSON_VALUE_ENUM_TYPE, value.getClass().getName());} else {// Optimistically create a JSONArray. If not an array type, we can null// it out laterjsonArray = new JSONArray();if (value instanceof byte[]) {supportedType = TYPE_BYTE_ARRAY;for (byte v : (byte[])value) {jsonArray.put((int)v);}} else if (value instanceof short[]) {supportedType = TYPE_SHORT_ARRAY;for (short v : (short[])value) {jsonArray.put((int)v);}} else if (value instanceof int[]) {supportedType = TYPE_INTEGER_ARRAY;for (int v : (int[])value) {jsonArray.put(v);}} else if (value instanceof long[]) {supportedType = TYPE_LONG_ARRAY;for (long v : (long[])value) {jsonArray.put(v);}} else if (value instanceof float[]) {supportedType = TYPE_FLOAT_ARRAY;for (float v : (float[])value) {jsonArray.put((double)v);}} else if (value instanceof double[]) {supportedType = TYPE_DOUBLE_ARRAY;for (double v : (double[])value) {jsonArray.put(v);}} else if (value instanceof boolean[]) {supportedType = TYPE_BOOLEAN_ARRAY;for (boolean v : (boolean[])value) {jsonArray.put(v);}} else if (value instanceof char[]) {supportedType = TYPE_CHAR_ARRAY;for (char v : (char[])value) {jsonArray.put(String.valueOf(v));}} else if (value instanceof List<?>) {supportedType = TYPE_STRING_LIST;@SuppressWarnings("unchecked")List<String> stringList = (List<String>)value;for (String v : stringList) {jsonArray.put((v == null) ? JSONObject.NULL : v);}} else {// Unsupported type. Clear out the array as a precaution even though// it is redundant with the null supportedType.jsonArray = null;}}if (supportedType != null) {json.put(JSON_VALUE_TYPE, supportedType);if (jsonArray != null) {// If we have an array, it has already been converted to JSON. So use// that instead.json.putOpt(JSON_VALUE, jsonArray);}String jsonString = json.toString();editor.putString(key, jsonString);}}private void deserializeKey(String key, Bundle bundle)throws JSONException {String jsonString = cache.getString(key, "{}");JSONObject json = new JSONObject(jsonString);String valueType = json.getString(JSON_VALUE_TYPE);if (valueType.equals(TYPE_BOOLEAN)) {bundle.putBoolean(key, json.getBoolean(JSON_VALUE));} else if (valueType.equals(TYPE_BOOLEAN_ARRAY)) {JSONArray jsonArray = json.getJSONArray(JSON_VALUE);boolean[] array = new boolean[jsonArray.length()];for (int i = 0; i < array.length; i++) {array[i] = jsonArray.getBoolean(i);}bundle.putBooleanArray(key, array);} else if (valueType.equals(TYPE_BYTE)) {bundle.putByte(key, (byte)json.getInt(JSON_VALUE));} else if (valueType.equals(TYPE_BYTE_ARRAY)) {JSONArray jsonArray = json.getJSONArray(JSON_VALUE);byte[] array = new byte[jsonArray.length()];for (int i = 0; i < array.length; i++) {array[i] = (byte)jsonArray.getInt(i);}bundle.putByteArray(key, array);} else if (valueType.equals(TYPE_SHORT)) {bundle.putShort(key, (short)json.getInt(JSON_VALUE));} else if (valueType.equals(TYPE_SHORT_ARRAY)) {JSONArray jsonArray = json.getJSONArray(JSON_VALUE);short[] array = new short[jsonArray.length()];for (int i = 0; i < array.length; i++) {array[i] = (short)jsonArray.getInt(i);}bundle.putShortArray(key, array);} else if (valueType.equals(TYPE_INTEGER)) {bundle.putInt(key, json.getInt(JSON_VALUE));} else if (valueType.equals(TYPE_INTEGER_ARRAY)) {JSONArray jsonArray = json.getJSONArray(JSON_VALUE);int[] array = new int[jsonArray.length()];for (int i = 0; i < array.length; i++) {array[i] = jsonArray.getInt(i);}bundle.putIntArray(key, array);} else if (valueType.equals(TYPE_LONG)) {bundle.putLong(key, json.getLong(JSON_VALUE));} else if (valueType.equals(TYPE_LONG_ARRAY)) {JSONArray jsonArray = json.getJSONArray(JSON_VALUE);long[] array = new long[jsonArray.length()];for (int i = 0; i < array.length; i++) {array[i] = jsonArray.getLong(i);}bundle.putLongArray(key, array);} else if (valueType.equals(TYPE_FLOAT)) {bundle.putFloat(key, (float)json.getDouble(JSON_VALUE));} else if (valueType.equals(TYPE_FLOAT_ARRAY)) {JSONArray jsonArray = json.getJSONArray(JSON_VALUE);float[] array = new float[jsonArray.length()];for (int i = 0; i < array.length; i++) {array[i] = (float)jsonArray.getDouble(i);}bundle.putFloatArray(key, array);} else if (valueType.equals(TYPE_DOUBLE)) {bundle.putDouble(key, json.getDouble(JSON_VALUE));} else if (valueType.equals(TYPE_DOUBLE_ARRAY)) {JSONArray jsonArray = json.getJSONArray(JSON_VALUE);double[] array = new double[jsonArray.length()];for (int i = 0; i < array.length; i++) {array[i] = jsonArray.getDouble(i);}bundle.putDoubleArray(key, array);} else if (valueType.equals(TYPE_CHAR)) {String charString = json.getString(JSON_VALUE);if (charString != null && charString.length() == 1) {bundle.putChar(key, charString.charAt(0));}} else if (valueType.equals(TYPE_CHAR_ARRAY)) {JSONArray jsonArray = json.getJSONArray(JSON_VALUE);char[] array = new char[jsonArray.length()];for (int i = 0; i < array.length; i++) {String charString = jsonArray.getString(i);if (charString != null && charString.length() == 1) {array[i] = charString.charAt(0);}}bundle.putCharArray(key, array);} else if (valueType.equals(TYPE_STRING)) {bundle.putString(key, json.getString(JSON_VALUE));} else if (valueType.equals(TYPE_STRING_LIST)) {JSONArray jsonArray = json.getJSONArray(JSON_VALUE);int numStrings = jsonArray.length();ArrayList<String> stringList = new ArrayList<String>(numStrings);for (int i = 0; i < numStrings; i++) {Object jsonStringValue = jsonArray.get(i);stringList.add(i, jsonStringValue == JSONObject.NULL ? null : (String)jsonStringValue);}bundle.putStringArrayList(key, stringList);} else if (valueType.equals(TYPE_ENUM)) {try {String enumType = json.getString(JSON_VALUE_ENUM_TYPE);@SuppressWarnings({ "unchecked", "rawtypes" })Class<? extends Enum> enumClass = (Class<? extends Enum>) Class.forName(enumType);@SuppressWarnings("unchecked")Enum<?> enumValue = Enum.valueOf(enumClass, json.getString(JSON_VALUE));bundle.putSerializable(key, enumValue);} catch (ClassNotFoundException e) {} catch (IllegalArgumentException e) {}}}}