Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
14792 manas 1
/**
2
 * Copyright 2010-present Facebook.
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 com.facebook;
18
 
19
import android.content.Context;
20
import android.content.SharedPreferences;
21
import android.os.Bundle;
22
import android.util.Log;
23
import com.facebook.internal.Logger;
24
import com.facebook.internal.Utility;
25
import com.facebook.internal.Validate;
26
import org.json.JSONArray;
27
import org.json.JSONException;
28
import org.json.JSONObject;
29
 
30
import java.util.ArrayList;
31
import java.util.List;
32
import java.util.Map;
33
 
34
/*
35
 * <p>
36
 * An implementation of {@link TokenCachingStrategy TokenCachingStrategy} that uses Android SharedPreferences
37
 * to persist information.
38
 * </p>
39
 * <p>
40
 * The data to be cached is passed in via a Bundle. Only non-null key-value-pairs where
41
 * the value is one of the following types (or an array of the same) are persisted:
42
 * boolean, byte, int, long, float, double, char. In addition, String and List<String>
43
 * are also supported.
44
 * </p>
45
 */
46
public class SharedPreferencesTokenCachingStrategy extends TokenCachingStrategy {
47
 
48
    private static final String DEFAULT_CACHE_KEY = "com.facebook.SharedPreferencesTokenCachingStrategy.DEFAULT_KEY";
49
    private static final String TAG = SharedPreferencesTokenCachingStrategy.class.getSimpleName();
50
 
51
    private static final String JSON_VALUE_TYPE = "valueType";
52
    private static final String JSON_VALUE = "value";
53
    private static final String JSON_VALUE_ENUM_TYPE = "enumType";
54
 
55
    private static final String TYPE_BOOLEAN = "bool";
56
    private static final String TYPE_BOOLEAN_ARRAY = "bool[]";
57
    private static final String TYPE_BYTE = "byte";
58
    private static final String TYPE_BYTE_ARRAY = "byte[]";
59
    private static final String TYPE_SHORT = "short";
60
    private static final String TYPE_SHORT_ARRAY = "short[]";
61
    private static final String TYPE_INTEGER = "int";
62
    private static final String TYPE_INTEGER_ARRAY = "int[]";
63
    private static final String TYPE_LONG = "long";
64
    private static final String TYPE_LONG_ARRAY = "long[]";
65
    private static final String TYPE_FLOAT = "float";
66
    private static final String TYPE_FLOAT_ARRAY = "float[]";
67
    private static final String TYPE_DOUBLE = "double";
68
    private static final String TYPE_DOUBLE_ARRAY = "double[]";
69
    private static final String TYPE_CHAR = "char";
70
    private static final String TYPE_CHAR_ARRAY = "char[]";
71
    private static final String TYPE_STRING = "string";
72
    private static final String TYPE_STRING_LIST = "stringList";
73
    private static final String TYPE_ENUM = "enum";
74
 
75
    private String cacheKey;
76
    private SharedPreferences cache;
77
 
78
    /**
79
     * Creates a default {@link SharedPreferencesTokenCachingStrategy SharedPreferencesTokenCachingStrategy}
80
     * instance that provides access to a single set of token information.
81
     *
82
     * @param context
83
     *              The Context object to use to get the SharedPreferences object.
84
     *
85
     * @throws NullPointerException if the passed in Context is null
86
     */
87
    public SharedPreferencesTokenCachingStrategy(Context context) {
88
        this(context, null);
89
    }
90
 
91
    /**
92
     * Creates a {@link SharedPreferencesTokenCachingStrategy SharedPreferencesTokenCachingStrategy} instance
93
     * that is distinct for the passed in cacheKey.
94
     *
95
     * @param context
96
     *              The Context object to use to get the SharedPreferences object.
97
     *
98
     * @param cacheKey
99
     *              Identifies a distinct set of token information.
100
     *
101
     * @throws NullPointerException if the passed in Context is null
102
     */
103
    public SharedPreferencesTokenCachingStrategy(Context context, String cacheKey) {
104
        Validate.notNull(context, "context");
105
 
106
        this.cacheKey = Utility.isNullOrEmpty(cacheKey) ? DEFAULT_CACHE_KEY : cacheKey;
107
 
108
        // If the application context is available, use that. However, if it isn't
109
        // available (possibly because of a context that was created manually), use
110
        // the passed in context directly.
111
        Context applicationContext = context.getApplicationContext();
112
        context = applicationContext != null ? applicationContext : context;
113
 
114
        this.cache = context.getSharedPreferences(
115
                this.cacheKey,
116
                Context.MODE_PRIVATE);
117
    }
118
 
119
    /**
120
     * Returns a Bundle that contains the information stored in this cache
121
     *
122
     * @return A Bundle with the information contained in this cache
123
     */
124
    public Bundle load() {
125
        Bundle settings = new Bundle();
126
 
127
        Map<String, ?> allCachedEntries = cache.getAll();
128
 
129
        for (String key : allCachedEntries.keySet()) {
130
            try {
131
                deserializeKey(key, settings);
132
            } catch (JSONException e) {
133
                // Error in the cache. So consider it corrupted and return null
134
                Logger.log(LoggingBehavior.CACHE, Log.WARN, TAG,
135
                        "Error reading cached value for key: '" + key + "' -- " + e);
136
                return null;
137
            }
138
        }
139
 
140
        return settings;
141
    }
142
 
143
    /**
144
     * Persists all supported data types present in the passed in Bundle, to the
145
     * cache
146
     *
147
     * @param bundle
148
     *          The Bundle containing information to be cached
149
     */
150
    public void save(Bundle bundle) {
151
        Validate.notNull(bundle, "bundle");
152
 
153
        SharedPreferences.Editor editor = cache.edit();
154
 
155
        for (String key : bundle.keySet()) {
156
            try {
157
                serializeKey(key, bundle, editor);
158
            } catch (JSONException e) {
159
                // Error in the bundle. Don't store a partial cache.
160
                Logger.log(LoggingBehavior.CACHE, Log.WARN, TAG, "Error processing value for key: '" + key + "' -- " + e);
161
 
162
                // Bypass the commit and just return. This cancels the entire edit transaction
163
                return;
164
            }
165
        }
166
        editor.apply();
167
    }
168
 
169
    /**
170
     * Clears out all token information stored in this cache.
171
     */
172
    public void clear() {
173
        cache.edit().clear().apply();
174
    }
175
 
176
    private void serializeKey(String key, Bundle bundle, SharedPreferences.Editor editor)
177
        throws JSONException {
178
        Object value = bundle.get(key);
179
        if (value == null) {
180
            // Cannot serialize null values.
181
            return;
182
        }
183
 
184
        String supportedType = null;
185
        JSONArray jsonArray = null;
186
        JSONObject json = new JSONObject();
187
 
188
        if (value instanceof Byte) {
189
            supportedType = TYPE_BYTE;
190
            json.put(JSON_VALUE, ((Byte)value).intValue());
191
        } else if (value instanceof Short) {
192
            supportedType = TYPE_SHORT;
193
            json.put(JSON_VALUE, ((Short)value).intValue());
194
        } else if (value instanceof Integer) {
195
            supportedType = TYPE_INTEGER;
196
            json.put(JSON_VALUE, ((Integer)value).intValue());
197
        } else if (value instanceof Long) {
198
            supportedType = TYPE_LONG;
199
            json.put(JSON_VALUE, ((Long)value).longValue());
200
        } else if (value instanceof Float) {
201
            supportedType = TYPE_FLOAT;
202
            json.put(JSON_VALUE, ((Float)value).doubleValue());
203
        } else if (value instanceof Double) {
204
            supportedType = TYPE_DOUBLE;
205
            json.put(JSON_VALUE, ((Double)value).doubleValue());
206
        } else if (value instanceof Boolean) {
207
            supportedType = TYPE_BOOLEAN;
208
            json.put(JSON_VALUE, ((Boolean)value).booleanValue());
209
        } else if (value instanceof Character) {
210
            supportedType = TYPE_CHAR;
211
            json.put(JSON_VALUE, value.toString());
212
        } else if (value instanceof String) {
213
            supportedType = TYPE_STRING;
214
            json.put(JSON_VALUE, (String)value);
215
        } else if (value instanceof Enum<?>) {
216
            supportedType = TYPE_ENUM;
217
            json.put(JSON_VALUE, value.toString());
218
            json.put(JSON_VALUE_ENUM_TYPE, value.getClass().getName());
219
        } else {
220
            // Optimistically create a JSONArray. If not an array type, we can null
221
            // it out later
222
            jsonArray = new JSONArray();
223
            if (value instanceof byte[]) {
224
                supportedType = TYPE_BYTE_ARRAY;
225
                for (byte v : (byte[])value) {
226
                    jsonArray.put((int)v);
227
                }
228
            } else if (value instanceof short[]) {
229
                supportedType = TYPE_SHORT_ARRAY;
230
                for (short v : (short[])value) {
231
                    jsonArray.put((int)v);
232
                }
233
            } else if (value instanceof int[]) {
234
                supportedType = TYPE_INTEGER_ARRAY;
235
                for (int v : (int[])value) {
236
                    jsonArray.put(v);
237
                }
238
            } else if (value instanceof long[]) {
239
                supportedType = TYPE_LONG_ARRAY;
240
                for (long v : (long[])value) {
241
                    jsonArray.put(v);
242
                }
243
            } else if (value instanceof float[]) {
244
                supportedType = TYPE_FLOAT_ARRAY;
245
                for (float v : (float[])value) {
246
                    jsonArray.put((double)v);
247
                }
248
            } else if (value instanceof double[]) {
249
                supportedType = TYPE_DOUBLE_ARRAY;
250
                for (double v : (double[])value) {
251
                    jsonArray.put(v);
252
                }
253
            } else if (value instanceof boolean[]) {
254
                supportedType = TYPE_BOOLEAN_ARRAY;
255
                for (boolean v : (boolean[])value) {
256
                    jsonArray.put(v);
257
                }
258
            } else if (value instanceof char[]) {
259
                supportedType = TYPE_CHAR_ARRAY;
260
                for (char v : (char[])value) {
261
                    jsonArray.put(String.valueOf(v));
262
                }
263
            } else if (value instanceof List<?>) {
264
                supportedType = TYPE_STRING_LIST;
265
                @SuppressWarnings("unchecked")
266
                List<String> stringList = (List<String>)value;
267
                for (String v : stringList) {
268
                    jsonArray.put((v == null) ? JSONObject.NULL : v);
269
                }
270
            } else {
271
                // Unsupported type. Clear out the array as a precaution even though
272
                // it is redundant with the null supportedType.
273
                jsonArray = null;
274
            }
275
        }
276
 
277
        if (supportedType != null) {
278
            json.put(JSON_VALUE_TYPE, supportedType);
279
            if (jsonArray != null) {
280
                // If we have an array, it has already been converted to JSON. So use
281
                // that instead.
282
                json.putOpt(JSON_VALUE, jsonArray);
283
            }
284
 
285
            String jsonString = json.toString();
286
            editor.putString(key, jsonString);
287
        }
288
    }
289
 
290
    private void deserializeKey(String key, Bundle bundle)
291
            throws JSONException {
292
        String jsonString = cache.getString(key, "{}");
293
        JSONObject json = new JSONObject(jsonString);
294
 
295
        String valueType = json.getString(JSON_VALUE_TYPE);
296
 
297
        if (valueType.equals(TYPE_BOOLEAN)) {
298
            bundle.putBoolean(key, json.getBoolean(JSON_VALUE));
299
        } else if (valueType.equals(TYPE_BOOLEAN_ARRAY)) {
300
            JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
301
            boolean[] array = new boolean[jsonArray.length()];
302
            for (int i = 0; i < array.length; i++) {
303
                array[i] = jsonArray.getBoolean(i);
304
            }
305
            bundle.putBooleanArray(key, array);
306
        } else if (valueType.equals(TYPE_BYTE)) {
307
            bundle.putByte(key, (byte)json.getInt(JSON_VALUE));
308
        } else if (valueType.equals(TYPE_BYTE_ARRAY)) {
309
            JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
310
            byte[] array = new byte[jsonArray.length()];
311
            for (int i = 0; i < array.length; i++) {
312
                array[i] = (byte)jsonArray.getInt(i);
313
            }
314
            bundle.putByteArray(key, array);
315
        } else if (valueType.equals(TYPE_SHORT)) {
316
            bundle.putShort(key, (short)json.getInt(JSON_VALUE));
317
        } else if (valueType.equals(TYPE_SHORT_ARRAY)) {
318
            JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
319
            short[] array = new short[jsonArray.length()];
320
            for (int i = 0; i < array.length; i++) {
321
                array[i] = (short)jsonArray.getInt(i);
322
            }
323
            bundle.putShortArray(key, array);
324
        } else if (valueType.equals(TYPE_INTEGER)) {
325
            bundle.putInt(key, json.getInt(JSON_VALUE));
326
        } else if (valueType.equals(TYPE_INTEGER_ARRAY)) {
327
            JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
328
            int[] array = new int[jsonArray.length()];
329
            for (int i = 0; i < array.length; i++) {
330
                array[i] = jsonArray.getInt(i);
331
            }
332
            bundle.putIntArray(key, array);
333
        } else if (valueType.equals(TYPE_LONG)) {
334
            bundle.putLong(key, json.getLong(JSON_VALUE));
335
        } else if (valueType.equals(TYPE_LONG_ARRAY)) {
336
            JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
337
            long[] array = new long[jsonArray.length()];
338
            for (int i = 0; i < array.length; i++) {
339
                array[i] = jsonArray.getLong(i);
340
            }
341
            bundle.putLongArray(key, array);
342
        } else if (valueType.equals(TYPE_FLOAT)) {
343
            bundle.putFloat(key, (float)json.getDouble(JSON_VALUE));
344
        } else if (valueType.equals(TYPE_FLOAT_ARRAY)) {
345
            JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
346
            float[] array = new float[jsonArray.length()];
347
            for (int i = 0; i < array.length; i++) {
348
                array[i] = (float)jsonArray.getDouble(i);
349
            }
350
            bundle.putFloatArray(key, array);
351
        } else if (valueType.equals(TYPE_DOUBLE)) {
352
            bundle.putDouble(key, json.getDouble(JSON_VALUE));
353
        } else if (valueType.equals(TYPE_DOUBLE_ARRAY)) {
354
            JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
355
            double[] array = new double[jsonArray.length()];
356
            for (int i = 0; i < array.length; i++) {
357
                array[i] = jsonArray.getDouble(i);
358
            }
359
            bundle.putDoubleArray(key, array);
360
        } else if (valueType.equals(TYPE_CHAR)) {
361
            String charString = json.getString(JSON_VALUE);
362
            if (charString != null && charString.length() == 1) {
363
                bundle.putChar(key, charString.charAt(0));
364
            }
365
        } else if (valueType.equals(TYPE_CHAR_ARRAY)) {
366
            JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
367
            char[] array = new char[jsonArray.length()];
368
            for (int i = 0; i < array.length; i++) {
369
                String charString = jsonArray.getString(i);
370
                if (charString != null && charString.length() == 1) {
371
                    array[i] = charString.charAt(0);
372
                }
373
            }
374
            bundle.putCharArray(key, array);
375
        } else if (valueType.equals(TYPE_STRING)) {
376
            bundle.putString(key, json.getString(JSON_VALUE));
377
        } else if (valueType.equals(TYPE_STRING_LIST)) {
378
            JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
379
            int numStrings = jsonArray.length();
380
            ArrayList<String> stringList = new ArrayList<String>(numStrings);
381
            for (int i = 0; i < numStrings; i++) {
382
                Object jsonStringValue = jsonArray.get(i);
383
                stringList.add(i, jsonStringValue == JSONObject.NULL ? null : (String)jsonStringValue);
384
            }
385
            bundle.putStringArrayList(key, stringList);
386
        } else if (valueType.equals(TYPE_ENUM)) {
387
            try {
388
                String enumType = json.getString(JSON_VALUE_ENUM_TYPE);
389
                @SuppressWarnings({ "unchecked", "rawtypes" })
390
                Class<? extends Enum> enumClass = (Class<? extends Enum>) Class.forName(enumType);
391
                @SuppressWarnings("unchecked")
392
                Enum<?> enumValue = Enum.valueOf(enumClass, json.getString(JSON_VALUE));
393
                bundle.putSerializable(key, enumValue);
394
            } catch (ClassNotFoundException e) {
395
            } catch (IllegalArgumentException e) {
396
            }
397
        }
398
    }
399
}