1/*
2 * Copyright (C) 2008 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.email;
18
19import android.content.Context;
20import android.content.SharedPreferences;
21import android.text.TextUtils;
22import android.util.Log;
23
24import com.android.emailcommon.Logging;
25import com.android.emailcommon.provider.Account;
26
27import org.json.JSONArray;
28import org.json.JSONException;
29
30import java.util.HashSet;
31import java.util.UUID;
32
33public class Preferences {
34
35    // Preferences file
36    public static final String PREFERENCES_FILE = "AndroidMail.Main";
37
38    // Preferences field names
39    private static final String ACCOUNT_UUIDS = "accountUuids";
40    private static final String ENABLE_DEBUG_LOGGING = "enableDebugLogging";
41    private static final String ENABLE_EXCHANGE_LOGGING = "enableExchangeLogging";
42    private static final String ENABLE_EXCHANGE_FILE_LOGGING = "enableExchangeFileLogging";
43    private static final String INHIBIT_GRAPHICS_ACCELERATION = "inhibitGraphicsAcceleration";
44    private static final String FORCE_ONE_MINUTE_REFRESH = "forceOneMinuteRefresh";
45    private static final String ENABLE_STRICT_MODE = "enableStrictMode";
46    private static final String DEVICE_UID = "deviceUID";
47    private static final String ONE_TIME_INITIALIZATION_PROGRESS = "oneTimeInitializationProgress";
48    private static final String AUTO_ADVANCE_DIRECTION = "autoAdvance";
49    private static final String TEXT_ZOOM = "textZoom";
50    private static final String BACKGROUND_ATTACHMENTS = "backgroundAttachments";
51    private static final String TRUSTED_SENDERS = "trustedSenders";
52    private static final String LAST_ACCOUNT_USED = "lastAccountUsed";
53    private static final String REQUIRE_MANUAL_SYNC_DIALOG_SHOWN = "requireManualSyncDialogShown";
54
55    public static final int AUTO_ADVANCE_NEWER = 0;
56    public static final int AUTO_ADVANCE_OLDER = 1;
57    public static final int AUTO_ADVANCE_MESSAGE_LIST = 2;
58    // "move to older" was the behavior on older versions.
59    private static final int AUTO_ADVANCE_DEFAULT = AUTO_ADVANCE_OLDER;
60
61    // The following constants are used as offsets into R.array.general_preference_text_zoom_size.
62    public static final int TEXT_ZOOM_TINY = 0;
63    public static final int TEXT_ZOOM_SMALL = 1;
64    public static final int TEXT_ZOOM_NORMAL = 2;
65    public static final int TEXT_ZOOM_LARGE = 3;
66    public static final int TEXT_ZOOM_HUGE = 4;
67    // "normal" will be the default
68    public static final int TEXT_ZOOM_DEFAULT = TEXT_ZOOM_NORMAL;
69
70    // Starting something new here:
71    // REPLY_ALL is saved by the framework (CheckBoxPreference's parent, Preference).
72    // i.e. android:persistent=true in general_preferences.xml
73    public static final String REPLY_ALL = "reply_all";
74    // Reply All Default - when changing this, be sure to update general_preferences.xml
75    public static final boolean REPLY_ALL_DEFAULT = false;
76
77    private static Preferences sPreferences;
78
79    private final SharedPreferences mSharedPreferences;
80
81    /**
82     * A set of trusted senders for whom images and external resources should automatically be
83     * loaded for.
84     * Lazilly created.
85     */
86    private HashSet<String> mTrustedSenders = null;
87
88    private Preferences(Context context) {
89        mSharedPreferences = context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
90    }
91
92    /**
93     * TODO need to think about what happens if this gets GCed along with the
94     * Activity that initialized it. Do we lose ability to read Preferences in
95     * further Activities? Maybe this should be stored in the Application
96     * context.
97     */
98    public static synchronized Preferences getPreferences(Context context) {
99        if (sPreferences == null) {
100            sPreferences = new Preferences(context);
101        }
102        return sPreferences;
103    }
104
105    public static SharedPreferences getSharedPreferences(Context context) {
106        return getPreferences(context).mSharedPreferences;
107    }
108
109    public static String getLegacyBackupPreference(Context context) {
110        return getPreferences(context).mSharedPreferences.getString(ACCOUNT_UUIDS, null);
111    }
112
113    public static void clearLegacyBackupPreference(Context context) {
114        getPreferences(context).mSharedPreferences.edit().remove(ACCOUNT_UUIDS).apply();
115    }
116
117    public void setEnableDebugLogging(boolean value) {
118        mSharedPreferences.edit().putBoolean(ENABLE_DEBUG_LOGGING, value).apply();
119    }
120
121    public boolean getEnableDebugLogging() {
122        return mSharedPreferences.getBoolean(ENABLE_DEBUG_LOGGING, false);
123    }
124
125    public void setEnableExchangeLogging(boolean value) {
126        mSharedPreferences.edit().putBoolean(ENABLE_EXCHANGE_LOGGING, value).apply();
127    }
128
129    public boolean getEnableExchangeLogging() {
130        return mSharedPreferences.getBoolean(ENABLE_EXCHANGE_LOGGING, false);
131    }
132
133    public void setEnableExchangeFileLogging(boolean value) {
134        mSharedPreferences.edit().putBoolean(ENABLE_EXCHANGE_FILE_LOGGING, value).apply();
135    }
136
137    public boolean getEnableExchangeFileLogging() {
138        return mSharedPreferences.getBoolean(ENABLE_EXCHANGE_FILE_LOGGING, false);
139    }
140
141    public void setInhibitGraphicsAcceleration(boolean value) {
142        mSharedPreferences.edit().putBoolean(INHIBIT_GRAPHICS_ACCELERATION, value).apply();
143    }
144
145    public boolean getInhibitGraphicsAcceleration() {
146        return mSharedPreferences.getBoolean(INHIBIT_GRAPHICS_ACCELERATION, false);
147    }
148
149    public void setForceOneMinuteRefresh(boolean value) {
150        mSharedPreferences.edit().putBoolean(FORCE_ONE_MINUTE_REFRESH, value).apply();
151    }
152
153    public boolean getForceOneMinuteRefresh() {
154        return mSharedPreferences.getBoolean(FORCE_ONE_MINUTE_REFRESH, false);
155    }
156
157    public void setEnableStrictMode(boolean value) {
158        mSharedPreferences.edit().putBoolean(ENABLE_STRICT_MODE, value).apply();
159    }
160
161    public boolean getEnableStrictMode() {
162        return mSharedPreferences.getBoolean(ENABLE_STRICT_MODE, false);
163    }
164
165    /**
166     * Generate a new "device UID".  This is local to Email app only, to prevent possibility
167     * of correlation with any other user activities in any other apps.
168     * @return a persistent, unique ID
169     */
170    public synchronized String getDeviceUID() {
171         String result = mSharedPreferences.getString(DEVICE_UID, null);
172         if (result == null) {
173             result = UUID.randomUUID().toString();
174             mSharedPreferences.edit().putString(DEVICE_UID, result).apply();
175         }
176         return result;
177    }
178
179    public int getOneTimeInitializationProgress() {
180        return mSharedPreferences.getInt(ONE_TIME_INITIALIZATION_PROGRESS, 0);
181    }
182
183    public void setOneTimeInitializationProgress(int progress) {
184        mSharedPreferences.edit().putInt(ONE_TIME_INITIALIZATION_PROGRESS, progress).apply();
185    }
186
187    public int getAutoAdvanceDirection() {
188        return mSharedPreferences.getInt(AUTO_ADVANCE_DIRECTION, AUTO_ADVANCE_DEFAULT);
189    }
190
191    public void setAutoAdvanceDirection(int direction) {
192        mSharedPreferences.edit().putInt(AUTO_ADVANCE_DIRECTION, direction).apply();
193    }
194
195    public int getTextZoom() {
196        return mSharedPreferences.getInt(TEXT_ZOOM, TEXT_ZOOM_DEFAULT);
197    }
198
199    public void setTextZoom(int zoom) {
200        mSharedPreferences.edit().putInt(TEXT_ZOOM, zoom).apply();
201    }
202
203    public boolean getBackgroundAttachments() {
204        return mSharedPreferences.getBoolean(BACKGROUND_ATTACHMENTS, false);
205    }
206
207    public void setBackgroundAttachments(boolean allowed) {
208        mSharedPreferences.edit().putBoolean(BACKGROUND_ATTACHMENTS, allowed).apply();
209    }
210
211    /**
212     * Determines whether or not a sender should be trusted and images should automatically be
213     * shown for messages by that sender.
214     */
215    public boolean shouldShowImagesFor(String email) {
216        if (mTrustedSenders == null) {
217            try {
218                mTrustedSenders = parseEmailSet(mSharedPreferences.getString(TRUSTED_SENDERS, ""));
219            } catch (JSONException e) {
220                // Something went wrong, and the data is corrupt. Just clear it to be safe.
221                Log.w(Logging.LOG_TAG, "Trusted sender set corrupted. Clearing");
222                mSharedPreferences.edit().putString(TRUSTED_SENDERS, "").apply();
223                mTrustedSenders = new HashSet<String>();
224            }
225        }
226        return mTrustedSenders.contains(email);
227    }
228
229    /**
230     * Marks a sender as trusted so that images from that sender will automatically be shown.
231     */
232    public void setSenderAsTrusted(String email) {
233        if (!mTrustedSenders.contains(email)) {
234            mTrustedSenders.add(email);
235            mSharedPreferences
236                    .edit()
237                    .putString(TRUSTED_SENDERS, packEmailSet(mTrustedSenders))
238                    .apply();
239        }
240    }
241
242    /**
243     * Clears all trusted senders asynchronously.
244     */
245    public void clearTrustedSenders() {
246        mTrustedSenders = new HashSet<String>();
247        mSharedPreferences
248                .edit()
249                .putString(TRUSTED_SENDERS, packEmailSet(mTrustedSenders))
250                .apply();
251    }
252
253    HashSet<String> parseEmailSet(String serialized) throws JSONException {
254        HashSet<String> result = new HashSet<String>();
255        if (!TextUtils.isEmpty(serialized)) {
256            JSONArray arr = new JSONArray(serialized);
257            for (int i = 0, len = arr.length(); i < len; i++) {
258                result.add((String) arr.get(i));
259            }
260        }
261        return result;
262    }
263
264    String packEmailSet(HashSet<String> set) {
265        return new JSONArray(set).toString();
266    }
267
268    /**
269     * Returns the last used account ID as set by {@link #setLastUsedAccountId}.
270     * The system makes no attempt to automatically track what is considered a "use" - clients
271     * are expected to call {@link #setLastUsedAccountId} manually.
272     *
273     * Note that the last used account may have been deleted in the background so there is also
274     * no guarantee that the account exists.
275     */
276    public long getLastUsedAccountId() {
277        return mSharedPreferences.getLong(LAST_ACCOUNT_USED, Account.NO_ACCOUNT);
278    }
279
280    /**
281     * Sets the specified ID of the last account used. Treated as an opaque ID and does not
282     * validate the value. Value is saved asynchronously.
283     */
284    public void setLastUsedAccountId(long accountId) {
285        mSharedPreferences
286                .edit()
287                .putLong(LAST_ACCOUNT_USED, accountId)
288                .apply();
289    }
290
291    /**
292     * Gets whether the require manual sync dialog has been shown for the specified account.
293     * It should only be shown once per account.
294     */
295    public boolean getHasShownRequireManualSync(Context context, Account account) {
296        return getBoolean(context, account.getEmailAddress(), REQUIRE_MANUAL_SYNC_DIALOG_SHOWN,
297                false);
298    }
299
300    /**
301     * Sets whether the require manual sync dialog has been shown for the specified account.
302     * It should only be shown once per account.
303     */
304    public void setHasShownRequireManualSync(Context context, Account account, boolean value) {
305        setBoolean(context, account.getEmailAddress(), REQUIRE_MANUAL_SYNC_DIALOG_SHOWN, value);
306    }
307
308
309    /**
310     * Get whether to show the manual sync dialog. This dialog is shown when the user is roaming,
311     * connected to a mobile network, the administrator has set the RequireManualSyncWhenRoaming
312     * flag to true, and the dialog has not been shown before for the supplied account.
313     */
314    public boolean shouldShowRequireManualSync(Context context, Account account) {
315        return Account.isAutomaticSyncDisabledByRoaming(context, account.mId)
316                && !getHasShownRequireManualSync(context, account);
317    }
318
319    public void clear() {
320        mSharedPreferences.edit().clear().apply();
321    }
322
323    public void dump() {
324        if (Logging.LOGD) {
325            for (String key : mSharedPreferences.getAll().keySet()) {
326                Log.v(Logging.LOG_TAG, key + " = " + mSharedPreferences.getAll().get(key));
327            }
328        }
329    }
330
331    /**
332     * Utility method for setting a boolean value on a per-account preference.
333     */
334    private void setBoolean(Context context, String account, String key, Boolean value) {
335        mSharedPreferences.edit().putBoolean(makeKey(account, key), value).apply();
336    }
337
338    /**
339     * Utility method for getting a boolean value from a per-account preference.
340     */
341    private boolean getBoolean(Context context, String account, String key, boolean def) {
342        return mSharedPreferences.getBoolean(makeKey(account, key), def);
343    }
344
345    /**
346     * Utility method for creating a per account preference key.
347     */
348    private String makeKey(String account, String key) {
349        return account != null ? account + "-" + key : key;
350    }
351}
352