1/*
2 * Copyright (C) 2010 The Android Open Source Project
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
17package com.android.camera;
18
19import android.app.backup.BackupManager;
20import android.content.Context;
21import android.content.SharedPreferences;
22import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
23import android.preference.PreferenceManager;
24
25import java.util.Map;
26import java.util.Set;
27import java.util.WeakHashMap;
28import java.util.concurrent.CopyOnWriteArrayList;
29
30import com.android.camera.util.UsageStatistics;
31
32public class ComboPreferences implements
33        SharedPreferences,
34        OnSharedPreferenceChangeListener {
35    private SharedPreferences mPrefGlobal;  // global preferences
36    private SharedPreferences mPrefLocal;  // per-camera preferences
37    private String mPackageName;
38    private CopyOnWriteArrayList<OnSharedPreferenceChangeListener> mListeners;
39    // TODO: Remove this WeakHashMap in the camera code refactoring
40    private static WeakHashMap<Context, ComboPreferences> sMap =
41            new WeakHashMap<Context, ComboPreferences>();
42
43    public ComboPreferences(Context context) {
44        mPackageName = context.getPackageName();
45        mPrefGlobal = context.getSharedPreferences(
46                getGlobalSharedPreferencesName(context), Context.MODE_PRIVATE);
47        mPrefGlobal.registerOnSharedPreferenceChangeListener(this);
48
49        synchronized (sMap) {
50            sMap.put(context, this);
51        }
52        mListeners = new CopyOnWriteArrayList<OnSharedPreferenceChangeListener>();
53
54        // The global preferences was previously stored in the default
55        // shared preferences file. They should be stored in the camera-specific
56        // shared preferences file so we can backup them solely.
57        SharedPreferences oldprefs =
58                PreferenceManager.getDefaultSharedPreferences(context);
59        if (!mPrefGlobal.contains(CameraSettings.KEY_VERSION)
60                && oldprefs.contains(CameraSettings.KEY_VERSION)) {
61            moveGlobalPrefsFrom(oldprefs);
62        }
63    }
64
65    public static ComboPreferences get(Context context) {
66        synchronized (sMap) {
67            return sMap.get(context);
68        }
69    }
70
71    private static String getLocalSharedPreferencesName(
72            Context context, int cameraId) {
73        return context.getPackageName() + "_preferences_" + cameraId;
74    }
75
76    private static String getGlobalSharedPreferencesName(Context context) {
77        return context.getPackageName() + "_preferences_camera";
78    }
79
80    private void movePrefFrom(
81            Map<String, ?> m, String key, SharedPreferences src) {
82        if (m.containsKey(key)) {
83            Object v = m.get(key);
84            if (v instanceof String) {
85                mPrefGlobal.edit().putString(key, (String) v).apply();
86            } else if (v instanceof Integer) {
87                mPrefGlobal.edit().putInt(key, (Integer) v).apply();
88            } else if (v instanceof Long) {
89                mPrefGlobal.edit().putLong(key, (Long) v).apply();
90            } else if (v instanceof Float) {
91                mPrefGlobal.edit().putFloat(key, (Float) v).apply();
92            } else if (v instanceof Boolean) {
93                mPrefGlobal.edit().putBoolean(key, (Boolean) v).apply();
94            }
95            src.edit().remove(key).apply();
96        }
97    }
98
99    private void moveGlobalPrefsFrom(SharedPreferences src) {
100        Map<String, ?> prefMap = src.getAll();
101        movePrefFrom(prefMap, CameraSettings.KEY_VERSION, src);
102        movePrefFrom(prefMap, CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL, src);
103        movePrefFrom(prefMap, CameraSettings.KEY_CAMERA_ID, src);
104        movePrefFrom(prefMap, CameraSettings.KEY_RECORD_LOCATION, src);
105        movePrefFrom(prefMap, CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, src);
106        movePrefFrom(prefMap, CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, src);
107        movePrefFrom(prefMap, CameraSettings.KEY_VIDEO_EFFECT, src);
108    }
109
110    public static String[] getSharedPreferencesNames(Context context) {
111        int numOfCameras = CameraHolder.instance().getNumberOfCameras();
112        String prefNames[] = new String[numOfCameras + 1];
113        prefNames[0] = getGlobalSharedPreferencesName(context);
114        for (int i = 0; i < numOfCameras; i++) {
115            prefNames[i + 1] = getLocalSharedPreferencesName(context, i);
116        }
117        return prefNames;
118    }
119
120    // Sets the camera id and reads its preferences. Each camera has its own
121    // preferences.
122    public void setLocalId(Context context, int cameraId) {
123        String prefName = getLocalSharedPreferencesName(context, cameraId);
124        if (mPrefLocal != null) {
125            mPrefLocal.unregisterOnSharedPreferenceChangeListener(this);
126        }
127        mPrefLocal = context.getSharedPreferences(
128                prefName, Context.MODE_PRIVATE);
129        mPrefLocal.registerOnSharedPreferenceChangeListener(this);
130    }
131
132    public SharedPreferences getGlobal() {
133        return mPrefGlobal;
134    }
135
136    public SharedPreferences getLocal() {
137        return mPrefLocal;
138    }
139
140    @Override
141    public Map<String, ?> getAll() {
142        throw new UnsupportedOperationException(); // Can be implemented if needed.
143    }
144
145    private static boolean isGlobal(String key) {
146        return key.equals(CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL)
147                || key.equals(CameraSettings.KEY_CAMERA_ID)
148                || key.equals(CameraSettings.KEY_RECORD_LOCATION)
149                || key.equals(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN)
150                || key.equals(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN)
151                || key.equals(CameraSettings.KEY_VIDEO_EFFECT)
152                || key.equals(CameraSettings.KEY_TIMER)
153                || key.equals(CameraSettings.KEY_TIMER_SOUND_EFFECTS)
154                || key.equals(CameraSettings.KEY_PHOTOSPHERE_PICTURESIZE);
155    }
156
157    @Override
158    public String getString(String key, String defValue) {
159        if (isGlobal(key) || !mPrefLocal.contains(key)) {
160            return mPrefGlobal.getString(key, defValue);
161        } else {
162            return mPrefLocal.getString(key, defValue);
163        }
164    }
165
166    @Override
167    public int getInt(String key, int defValue) {
168        if (isGlobal(key) || !mPrefLocal.contains(key)) {
169            return mPrefGlobal.getInt(key, defValue);
170        } else {
171            return mPrefLocal.getInt(key, defValue);
172        }
173    }
174
175    @Override
176    public long getLong(String key, long defValue) {
177        if (isGlobal(key) || !mPrefLocal.contains(key)) {
178            return mPrefGlobal.getLong(key, defValue);
179        } else {
180            return mPrefLocal.getLong(key, defValue);
181        }
182    }
183
184    @Override
185    public float getFloat(String key, float defValue) {
186        if (isGlobal(key) || !mPrefLocal.contains(key)) {
187            return mPrefGlobal.getFloat(key, defValue);
188        } else {
189            return mPrefLocal.getFloat(key, defValue);
190        }
191    }
192
193    @Override
194    public boolean getBoolean(String key, boolean defValue) {
195        if (isGlobal(key) || !mPrefLocal.contains(key)) {
196            return mPrefGlobal.getBoolean(key, defValue);
197        } else {
198            return mPrefLocal.getBoolean(key, defValue);
199        }
200    }
201
202    // This method is not used.
203    @Override
204    public Set<String> getStringSet(String key, Set<String> defValues) {
205        throw new UnsupportedOperationException();
206    }
207
208    @Override
209    public boolean contains(String key) {
210        return mPrefLocal.contains(key) || mPrefGlobal.contains(key);
211    }
212
213    private class MyEditor implements Editor {
214        private Editor mEditorGlobal;
215        private Editor mEditorLocal;
216
217        MyEditor() {
218            mEditorGlobal = mPrefGlobal.edit();
219            mEditorLocal = mPrefLocal.edit();
220        }
221
222        @Override
223        public boolean commit() {
224            boolean result1 = mEditorGlobal.commit();
225            boolean result2 = mEditorLocal.commit();
226            return result1 && result2;
227        }
228
229        @Override
230        public void apply() {
231            mEditorGlobal.apply();
232            mEditorLocal.apply();
233        }
234
235        // Note: clear() and remove() affects both local and global preferences.
236        @Override
237        public Editor clear() {
238            mEditorGlobal.clear();
239            mEditorLocal.clear();
240            return this;
241        }
242
243        @Override
244        public Editor remove(String key) {
245            mEditorGlobal.remove(key);
246            mEditorLocal.remove(key);
247            return this;
248        }
249
250        @Override
251        public Editor putString(String key, String value) {
252            if (isGlobal(key)) {
253                mEditorGlobal.putString(key, value);
254            } else {
255                mEditorLocal.putString(key, value);
256            }
257            return this;
258        }
259
260        @Override
261        public Editor putInt(String key, int value) {
262            if (isGlobal(key)) {
263                mEditorGlobal.putInt(key, value);
264            } else {
265                mEditorLocal.putInt(key, value);
266            }
267            return this;
268        }
269
270        @Override
271        public Editor putLong(String key, long value) {
272            if (isGlobal(key)) {
273                mEditorGlobal.putLong(key, value);
274            } else {
275                mEditorLocal.putLong(key, value);
276            }
277            return this;
278        }
279
280        @Override
281        public Editor putFloat(String key, float value) {
282            if (isGlobal(key)) {
283                mEditorGlobal.putFloat(key, value);
284            } else {
285                mEditorLocal.putFloat(key, value);
286            }
287            return this;
288        }
289
290        @Override
291        public Editor putBoolean(String key, boolean value) {
292            if (isGlobal(key)) {
293                mEditorGlobal.putBoolean(key, value);
294            } else {
295                mEditorLocal.putBoolean(key, value);
296            }
297            return this;
298        }
299
300        // This method is not used.
301        @Override
302        public Editor putStringSet(String key, Set<String> values) {
303            throw new UnsupportedOperationException();
304        }
305    }
306
307    // Note the remove() and clear() of the returned Editor may not work as
308    // expected because it doesn't touch the global preferences at all.
309    @Override
310    public Editor edit() {
311        return new MyEditor();
312    }
313
314    @Override
315    public void registerOnSharedPreferenceChangeListener(
316            OnSharedPreferenceChangeListener listener) {
317        mListeners.add(listener);
318    }
319
320    @Override
321    public void unregisterOnSharedPreferenceChangeListener(
322            OnSharedPreferenceChangeListener listener) {
323        mListeners.remove(listener);
324    }
325
326    @Override
327    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
328            String key) {
329        for (OnSharedPreferenceChangeListener listener : mListeners) {
330            listener.onSharedPreferenceChanged(this, key);
331        }
332        BackupManager.dataChanged(mPackageName);
333    }
334}
335