1 /*
2  * Copyright (C) 2012 Google Inc.
3  * Licensed to The Android Open Source Project.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.mail.preferences;
19 
20 import android.content.Context;
21 import android.content.SharedPreferences;
22 import android.support.annotation.StringDef;
23 import android.text.TextUtils;
24 
25 import com.android.mail.R;
26 import com.android.mail.providers.Account;
27 import com.android.mail.providers.UIProvider;
28 import com.android.mail.utils.LogUtils;
29 import com.android.mail.widget.BaseWidgetProvider;
30 import com.google.common.annotations.VisibleForTesting;
31 import com.google.common.collect.ImmutableSet;
32 import com.google.common.collect.Sets;
33 
34 import java.lang.annotation.Retention;
35 import java.lang.annotation.RetentionPolicy;
36 import java.util.Collections;
37 import java.util.List;
38 import java.util.Set;
39 import java.util.regex.Pattern;
40 
41 /**
42  * A high-level API to store and retrieve unified mail preferences.
43  * <p>
44  * This will serve as an eventual replacement for Gmail's Persistence class.
45  */
46 public final class MailPrefs extends VersionedPrefs {
47 
48     public static final boolean SHOW_EXPERIMENTAL_PREFS = false;
49 
50     private static final String PREFS_NAME = "UnifiedEmail";
51 
52     private static MailPrefs sInstance;
53 
54     private final int mSnapHeaderDefault;
55 
56     public static final class PreferenceKeys {
57         private static final String MIGRATED_VERSION = "migrated-version";
58 
59         public static final String WIDGET_ACCOUNT_PREFIX = "widget-account-";
60 
61         /** Hidden preference to indicate what version a "What's New" dialog was last shown for. */
62         public static final String WHATS_NEW_LAST_SHOWN_VERSION = "whats-new-last-shown-version";
63 
64         /**
65          * A boolean that, if <code>true</code>, means we should default all replies to "reply all"
66          */
67         public static final String DEFAULT_REPLY_ALL = "default-reply-all";
68         /**
69          * A boolean that, if <code>true</code>, means we should allow conversation list swiping
70          */
71         public static final String CONVERSATION_LIST_SWIPE = "conversation-list-swipe";
72 
73         /** A string indicating the user's removal action preference. */
74         public static final String REMOVAL_ACTION = "removal-action";
75 
76         /** Hidden preference used to cache the active notification set */
77         private static final String CACHED_ACTIVE_NOTIFICATION_SET =
78                 "cache-active-notification-set";
79 
80         /**
81          * A string indicating whether the conversation photo teaser has been previously
82          * shown and dismissed. This is the third version of it (thus the three at the end).
83          * Previous versions: "conversation-photo-teaser-shown"
84          * and "conversation-photo-teaser-shown-two".
85          */
86         private static final String
87                 CONVERSATION_PHOTO_TEASER_SHOWN = "conversation-photo-teaser-shown-three";
88 
89         public static final String DISPLAY_IMAGES = "display_images";
90         public static final String DISPLAY_IMAGES_PATTERNS = "display_sender_images_patterns_set";
91 
92 
93         public static final String SHOW_SENDER_IMAGES = "conversation-list-sender-image";
94 
95         public static final String
96                 LONG_PRESS_TO_SELECT_TIP_SHOWN = "long-press-to-select-tip-shown";
97 
98         /** @deprecated attachment previews have been removed; avoid future key name conflicts */
99         public static final String EXPERIMENT_AP_PARALLAX_SPEED_ALTERNATIVE = "ap-parallax-speed";
100 
101         /** @deprecated attachment previews have been removed; avoid future key name conflicts */
102         public static final String EXPERIMENT_AP_PARALLAX_DIRECTION_ALTERNATIVE
103                 = "ap-parallax-direction";
104 
105         public static final String GLOBAL_SYNC_OFF_DISMISSES = "num-of-dismisses-auto-sync-off";
106         public static final String AIRPLANE_MODE_ON_DISMISSES = "num-of-dismisses-airplane-mode-on";
107 
108         public static final String AUTO_ADVANCE_MODE = "auto-advance-mode";
109 
110         public static final String CONFIRM_DELETE = "confirm-delete";
111         public static final String CONFIRM_ARCHIVE = "confirm-archive";
112         public static final String CONFIRM_SEND = "confirm-send";
113 
114         public static final String CONVERSATION_OVERVIEW_MODE = "conversation-overview-mode";
115 
116         public static final String ALWAYS_LAUNCH_GMAIL_FROM_EMAIL_TOMBSTONE =
117                 "always-launch-gmail-from-email-tombstone";
118 
119         public static final String SNAP_HEADER_MODE = "snap-header-mode";
120 
121         public static final String RECENT_ACCOUNTS = "recent-accounts";
122 
123         public static final String REQUIRED_SANITIZER_VERSION_NUMBER =
124                 "required-sanitizer-version-number";
125 
126         public static final String MIGRATION_STATE = "migration-state";
127 
128         /**
129          * The time in epoch ms when the number of accounts in the app was reported to analytics.
130          */
131         public static final String ANALYTICS_NB_ACCOUNT_LATEST_REPORT =
132                 "analytics-send-nb_accounts-epoch";
133 
134         // State indicating that no migration has yet occurred.
135         public static final int MIGRATION_STATE_NONE = 0;
136         // State indicating that we have migrated imap and pop accounts, but not
137         // Exchange accounts.
138         public static final int MIGRATION_STATE_IMAP_POP = 1;
139         // State indicating that we have migrated all accounts.
140         public static final int MIGRATION_STATE_ALL = 2;
141 
142         public static final ImmutableSet<String> BACKUP_KEYS =
143                 new ImmutableSet.Builder<String>()
144                 .add(DEFAULT_REPLY_ALL)
145                 .add(CONVERSATION_LIST_SWIPE)
146                 .add(REMOVAL_ACTION)
147                 .add(DISPLAY_IMAGES)
148                 .add(DISPLAY_IMAGES_PATTERNS)
149                 .add(SHOW_SENDER_IMAGES)
150                 .add(LONG_PRESS_TO_SELECT_TIP_SHOWN)
151                 .add(AUTO_ADVANCE_MODE)
152                 .add(CONFIRM_DELETE)
153                 .add(CONFIRM_ARCHIVE)
154                 .add(CONFIRM_SEND)
155                 .add(CONVERSATION_OVERVIEW_MODE)
156                 .add(SNAP_HEADER_MODE)
157                 .build();
158     }
159 
160     public static final class ConversationListSwipeActions {
161         public static final String ARCHIVE = "archive";
162         public static final String DELETE = "delete";
163         public static final String DISABLED = "disabled";
164     }
165 
166     @Retention(RetentionPolicy.SOURCE)
167     @StringDef({
168             RemovalActions.ARCHIVE,
169             RemovalActions.DELETE
170     })
171     public @interface RemovalActionTypes {}
172     public static final class RemovalActions {
173         public static final String ARCHIVE = "archive";
174         public static final String DELETE = "delete";
175         @Deprecated
176         public static final String ARCHIVE_AND_DELETE = "archive-and-delete";
177     }
178 
get(final Context c)179     public static synchronized MailPrefs get(final Context c) {
180         if (sInstance == null) {
181             sInstance = new MailPrefs(c, PREFS_NAME);
182         }
183         return sInstance;
184     }
185 
186     @VisibleForTesting
MailPrefs(final Context c, final String prefsName)187     public MailPrefs(final Context c, final String prefsName) {
188         super(c, prefsName);
189         mSnapHeaderDefault = c.getResources().getInteger(R.integer.prefDefault_snapHeader);
190     }
191 
192     @Override
performUpgrade(final int oldVersion, final int newVersion)193     protected void performUpgrade(final int oldVersion, final int newVersion) {
194         if (oldVersion > newVersion) {
195             throw new IllegalStateException(
196                     "You appear to have downgraded your app. Please clear app data.");
197         } else if (oldVersion == newVersion) {
198             return;
199         }
200     }
201 
202     @Override
canBackup(final String key)203     protected boolean canBackup(final String key) {
204         return PreferenceKeys.BACKUP_KEYS.contains(key);
205     }
206 
207     @Override
hasMigrationCompleted()208     protected boolean hasMigrationCompleted() {
209         return getSharedPreferences().getInt(PreferenceKeys.MIGRATED_VERSION, 0)
210                 >= CURRENT_VERSION_NUMBER;
211     }
212 
213     @Override
setMigrationComplete()214     protected void setMigrationComplete() {
215         getEditor().putInt(PreferenceKeys.MIGRATED_VERSION, CURRENT_VERSION_NUMBER).commit();
216     }
217 
isWidgetConfigured(int appWidgetId)218     public boolean isWidgetConfigured(int appWidgetId) {
219         return getSharedPreferences().contains(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + appWidgetId);
220     }
221 
configureWidget(int appWidgetId, Account account, final String folderUri)222     public void configureWidget(int appWidgetId, Account account, final String folderUri) {
223         if (account == null) {
224             LogUtils.e(LOG_TAG, "Cannot configure widget with null account");
225             return;
226         }
227         getEditor().putString(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + appWidgetId,
228                 createWidgetPreferenceValue(account, folderUri)).apply();
229     }
230 
getWidgetConfiguration(int appWidgetId)231     public String getWidgetConfiguration(int appWidgetId) {
232         return getSharedPreferences().getString(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + appWidgetId,
233                 null);
234     }
235 
createWidgetPreferenceValue(Account account, String folderUri)236     private static String createWidgetPreferenceValue(Account account, String folderUri) {
237         return account.uri.toString() + BaseWidgetProvider.ACCOUNT_FOLDER_PREFERENCE_SEPARATOR
238                 + folderUri;
239 
240     }
241 
clearWidgets(int[] appWidgetIds)242     public void clearWidgets(int[] appWidgetIds) {
243         for (int id : appWidgetIds) {
244             getEditor().remove(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + id);
245         }
246         getEditor().apply();
247     }
248 
249     /** If <code>true</code>, we should default all replies to "reply all" rather than "reply" */
getDefaultReplyAll()250     public boolean getDefaultReplyAll() {
251         return getSharedPreferences().getBoolean(PreferenceKeys.DEFAULT_REPLY_ALL, false);
252     }
253 
setDefaultReplyAll(final boolean replyAll)254     public void setDefaultReplyAll(final boolean replyAll) {
255         getEditor().putBoolean(PreferenceKeys.DEFAULT_REPLY_ALL, replyAll).apply();
256         notifyBackupPreferenceChanged();
257     }
258 
259     /**
260      * Returns a string indicating the preferred removal action.
261      * Should be one of the {@link RemovalActions}.
262      */
getRemovalAction(final boolean supportsArchive)263     public String getRemovalAction(final boolean supportsArchive) {
264         if (!supportsArchive) {
265             return RemovalActions.DELETE;
266         }
267 
268         final SharedPreferences sharedPreferences = getSharedPreferences();
269         final String removalAction =
270                 sharedPreferences.getString(PreferenceKeys.REMOVAL_ACTION, null);
271         if (TextUtils.equals(removalAction, RemovalActions.ARCHIVE_AND_DELETE)) {
272             return RemovalActions.ARCHIVE;
273         }
274         return sharedPreferences.getString(PreferenceKeys.REMOVAL_ACTION,
275                 RemovalActions.ARCHIVE);
276     }
277 
278     /**
279      * Sets the removal action preference.
280      * @param removalAction The preferred {@link RemovalActions}.
281      */
setRemovalAction(final @RemovalActionTypes String removalAction)282     public void setRemovalAction(final @RemovalActionTypes String removalAction) {
283         getEditor().putString(PreferenceKeys.REMOVAL_ACTION, removalAction).apply();
284         notifyBackupPreferenceChanged();
285     }
286 
287     /**
288      * Gets a boolean indicating whether conversation list swiping is enabled.
289      */
getIsConversationListSwipeEnabled()290     public boolean getIsConversationListSwipeEnabled() {
291         final SharedPreferences sharedPreferences = getSharedPreferences();
292         return sharedPreferences.getBoolean(PreferenceKeys.CONVERSATION_LIST_SWIPE, true);
293     }
294 
setConversationListSwipeEnabled(final boolean enabled)295     public void setConversationListSwipeEnabled(final boolean enabled) {
296         getEditor().putBoolean(PreferenceKeys.CONVERSATION_LIST_SWIPE, enabled).apply();
297         notifyBackupPreferenceChanged();
298     }
299 
300     /**
301      * Gets the action to take (one of the values from {@link UIProvider.Swipe}) when an item in the
302      * conversation list is swiped.
303      *
304      * @param allowArchive <code>true</code> if Archive is an acceptable action (this will affect
305      *        the default return value)
306      */
getConversationListSwipeActionInteger(final boolean allowArchive)307     public int getConversationListSwipeActionInteger(final boolean allowArchive) {
308         final boolean swipeEnabled = getIsConversationListSwipeEnabled();
309         final boolean archive = !RemovalActions.DELETE.equals(getRemovalAction(allowArchive));
310 
311         if (swipeEnabled) {
312             return archive ? UIProvider.Swipe.ARCHIVE : UIProvider.Swipe.DELETE;
313         }
314 
315         return UIProvider.Swipe.DISABLED;
316     }
317 
318     /**
319      * Returns the previously cached notification set
320      */
getActiveNotificationSet()321     public Set<String> getActiveNotificationSet() {
322         return getSharedPreferences()
323                 .getStringSet(PreferenceKeys.CACHED_ACTIVE_NOTIFICATION_SET, null);
324     }
325 
326     /**
327      * Caches the current notification set.
328      */
cacheActiveNotificationSet(final Set<String> notificationSet)329     public void cacheActiveNotificationSet(final Set<String> notificationSet) {
330         getEditor().putStringSet(PreferenceKeys.CACHED_ACTIVE_NOTIFICATION_SET, notificationSet)
331                 .apply();
332     }
333 
334     /**
335      * Returns whether the teaser has been shown before
336      */
isConversationPhotoTeaserAlreadyShown()337     public boolean isConversationPhotoTeaserAlreadyShown() {
338         return getSharedPreferences()
339                 .getBoolean(PreferenceKeys.CONVERSATION_PHOTO_TEASER_SHOWN, false);
340     }
341 
342     /**
343      * Notify that we have shown the teaser
344      */
setConversationPhotoTeaserAlreadyShown()345     public void setConversationPhotoTeaserAlreadyShown() {
346         getEditor().putBoolean(PreferenceKeys.CONVERSATION_PHOTO_TEASER_SHOWN, true).apply();
347     }
348 
349     /**
350      * Returns whether the tip has been shown before
351      */
isLongPressToSelectTipAlreadyShown()352     public boolean isLongPressToSelectTipAlreadyShown() {
353         // Using an int instead of boolean here in case we need to reshow the tip (don't have
354         // to use a new preference name).
355         return getSharedPreferences()
356                 .getInt(PreferenceKeys.LONG_PRESS_TO_SELECT_TIP_SHOWN, 0) > 0;
357     }
358 
setLongPressToSelectTipAlreadyShown()359     public void setLongPressToSelectTipAlreadyShown() {
360         getEditor().putInt(PreferenceKeys.LONG_PRESS_TO_SELECT_TIP_SHOWN, 1).apply();
361         notifyBackupPreferenceChanged();
362     }
363 
setSenderWhitelist(Set<String> addresses)364     public void setSenderWhitelist(Set<String> addresses) {
365         getEditor().putStringSet(PreferenceKeys.DISPLAY_IMAGES, addresses).apply();
366         notifyBackupPreferenceChanged();
367     }
setSenderWhitelistPatterns(Set<String> patterns)368     public void setSenderWhitelistPatterns(Set<String> patterns) {
369         getEditor().putStringSet(PreferenceKeys.DISPLAY_IMAGES_PATTERNS, patterns).apply();
370         notifyBackupPreferenceChanged();
371     }
372 
373     /**
374      * Returns whether or not an email address is in the whitelist of senders to show images for.
375      * This method reads the entire whitelist, so if you have multiple emails to check, you should
376      * probably call getSenderWhitelist() and check membership yourself.
377      *
378      * @param sender raw email address ("foo@bar.com")
379      * @return whether we should show pictures for this sender
380      */
getDisplayImagesFromSender(String sender)381     public boolean getDisplayImagesFromSender(String sender) {
382         boolean displayImages = getSenderWhitelist().contains(sender);
383         if (!displayImages) {
384             final SharedPreferences sharedPreferences = getSharedPreferences();
385             // Check the saved email address patterns to determine if this pattern matches
386             final Set<String> defaultPatternSet = Collections.emptySet();
387             final Set<String> currentPatterns = sharedPreferences.getStringSet(
388                         PreferenceKeys.DISPLAY_IMAGES_PATTERNS, defaultPatternSet);
389             for (String pattern : currentPatterns) {
390                 displayImages = Pattern.compile(pattern).matcher(sender).matches();
391                 if (displayImages) {
392                     break;
393                 }
394             }
395         }
396 
397         return displayImages;
398     }
399 
400 
setDisplayImagesFromSender(String sender, List<Pattern> allowedPatterns)401     public void setDisplayImagesFromSender(String sender, List<Pattern> allowedPatterns) {
402         if (allowedPatterns != null) {
403             // Look at the list of patterns where we want to allow a particular class of
404             // email address
405             for (Pattern pattern : allowedPatterns) {
406                 if (pattern.matcher(sender).matches()) {
407                     // The specified email address matches one of the social network patterns.
408                     // Save the pattern itself
409                     final Set<String> currentPatterns = getSenderWhitelistPatterns();
410                     final String patternRegex = pattern.pattern();
411                     if (!currentPatterns.contains(patternRegex)) {
412                         // Copy strings to a modifiable set
413                         final Set<String> updatedPatterns = Sets.newHashSet(currentPatterns);
414                         updatedPatterns.add(patternRegex);
415                         setSenderWhitelistPatterns(updatedPatterns);
416                     }
417                     return;
418                 }
419             }
420         }
421         final Set<String> whitelist = getSenderWhitelist();
422         if (!whitelist.contains(sender)) {
423             // Storing a JSONObject is slightly more nice in that maps are guaranteed to not have
424             // duplicate entries, but using a Set as intermediate representation guarantees this
425             // for us anyway. Also, using maps to represent sets forces you to pick values for
426             // them, and that's weird.
427             final Set<String> updatedList = Sets.newHashSet(whitelist);
428             updatedList.add(sender);
429             setSenderWhitelist(updatedList);
430         }
431     }
432 
getSenderWhitelist()433     private Set<String> getSenderWhitelist() {
434         final SharedPreferences sharedPreferences = getSharedPreferences();
435         final Set<String> defaultAddressSet = Collections.emptySet();
436         return sharedPreferences.getStringSet(PreferenceKeys.DISPLAY_IMAGES, defaultAddressSet);
437     }
438 
439 
getSenderWhitelistPatterns()440     private Set<String> getSenderWhitelistPatterns() {
441         final SharedPreferences sharedPreferences = getSharedPreferences();
442         final Set<String> defaultPatternSet = Collections.emptySet();
443         return sharedPreferences.getStringSet(PreferenceKeys.DISPLAY_IMAGES_PATTERNS,
444                 defaultPatternSet);
445     }
446 
clearSenderWhiteList()447     public void clearSenderWhiteList() {
448         final SharedPreferences.Editor editor = getEditor();
449         editor.putStringSet(PreferenceKeys.DISPLAY_IMAGES, Collections.EMPTY_SET);
450         editor.putStringSet(PreferenceKeys.DISPLAY_IMAGES_PATTERNS, Collections.EMPTY_SET);
451         editor.apply();
452     }
453 
setShowSenderImages(boolean enable)454     public void setShowSenderImages(boolean enable) {
455         getEditor().putBoolean(PreferenceKeys.SHOW_SENDER_IMAGES, enable).apply();
456         notifyBackupPreferenceChanged();
457     }
458 
getShowSenderImages()459     public boolean getShowSenderImages() {
460         final SharedPreferences sharedPreferences = getSharedPreferences();
461         return sharedPreferences.getBoolean(PreferenceKeys.SHOW_SENDER_IMAGES, true);
462     }
463 
getNumOfDismissesForAutoSyncOff()464     public int getNumOfDismissesForAutoSyncOff() {
465         return getSharedPreferences().getInt(PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, 0);
466     }
467 
resetNumOfDismissesForAutoSyncOff()468     public void resetNumOfDismissesForAutoSyncOff() {
469         final int value = getSharedPreferences().getInt(
470                 PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, 0);
471         if (value != 0) {
472             getEditor().putInt(PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, 0).apply();
473         }
474     }
475 
incNumOfDismissesForAutoSyncOff()476     public void incNumOfDismissesForAutoSyncOff() {
477         final int value = getSharedPreferences().getInt(
478                 PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, 0);
479         getEditor().putInt(PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, value + 1).apply();
480     }
481 
setConfirmDelete(final boolean confirmDelete)482     public void setConfirmDelete(final boolean confirmDelete) {
483         getEditor().putBoolean(PreferenceKeys.CONFIRM_DELETE, confirmDelete).apply();
484         notifyBackupPreferenceChanged();
485     }
486 
getConfirmDelete()487     public boolean getConfirmDelete() {
488         return getSharedPreferences().getBoolean(PreferenceKeys.CONFIRM_DELETE, false);
489     }
490 
setConfirmArchive(final boolean confirmArchive)491     public void setConfirmArchive(final boolean confirmArchive) {
492         getEditor().putBoolean(PreferenceKeys.CONFIRM_ARCHIVE, confirmArchive).apply();
493         notifyBackupPreferenceChanged();
494     }
495 
getConfirmArchive()496     public boolean getConfirmArchive() {
497         return getSharedPreferences().getBoolean(PreferenceKeys.CONFIRM_ARCHIVE, false);
498     }
499 
setConfirmSend(final boolean confirmSend)500     public void setConfirmSend(final boolean confirmSend) {
501         getEditor().putBoolean(PreferenceKeys.CONFIRM_SEND, confirmSend).apply();
502         notifyBackupPreferenceChanged();
503     }
504 
getConfirmSend()505     public boolean getConfirmSend() {
506         return getSharedPreferences().getBoolean(PreferenceKeys.CONFIRM_SEND, false);
507     }
508 
setAutoAdvanceMode(final int mode)509     public void setAutoAdvanceMode(final int mode) {
510         getEditor().putInt(PreferenceKeys.AUTO_ADVANCE_MODE, mode).apply();
511         notifyBackupPreferenceChanged();
512     }
513 
getAutoAdvanceMode()514     public int getAutoAdvanceMode() {
515         return getSharedPreferences()
516                 .getInt(PreferenceKeys.AUTO_ADVANCE_MODE, UIProvider.AutoAdvance.DEFAULT);
517     }
518 
setConversationOverviewMode(final boolean overviewMode)519     public void setConversationOverviewMode(final boolean overviewMode) {
520         getEditor().putBoolean(PreferenceKeys.CONVERSATION_OVERVIEW_MODE, overviewMode).apply();
521     }
522 
getConversationOverviewMode()523     public boolean getConversationOverviewMode() {
524         return getSharedPreferences()
525                 .getBoolean(PreferenceKeys.CONVERSATION_OVERVIEW_MODE, true);
526     }
527 
isConversationOverviewModeSet()528     public boolean isConversationOverviewModeSet() {
529         return getSharedPreferences().contains(PreferenceKeys.CONVERSATION_OVERVIEW_MODE);
530     }
531 
setAlwaysLaunchGmailFromEmailTombstone(final boolean alwaysLaunchGmail)532     public void setAlwaysLaunchGmailFromEmailTombstone(final boolean alwaysLaunchGmail) {
533         getEditor()
534                 .putBoolean(PreferenceKeys.ALWAYS_LAUNCH_GMAIL_FROM_EMAIL_TOMBSTONE,
535                         alwaysLaunchGmail)
536                 .apply();
537     }
538 
getAlwaysLaunchGmailFromEmailTombstone()539     public boolean getAlwaysLaunchGmailFromEmailTombstone() {
540         return getSharedPreferences()
541                 .getBoolean(PreferenceKeys.ALWAYS_LAUNCH_GMAIL_FROM_EMAIL_TOMBSTONE, false);
542     }
543 
setSnapHeaderMode(final int snapHeaderMode)544     public void setSnapHeaderMode(final int snapHeaderMode) {
545         getEditor().putInt(PreferenceKeys.SNAP_HEADER_MODE, snapHeaderMode).apply();
546     }
547 
getSnapHeaderMode()548     public int getSnapHeaderMode() {
549         return getSharedPreferences()
550                 .getInt(PreferenceKeys.SNAP_HEADER_MODE, mSnapHeaderDefault);
551     }
552 
getSnapHeaderDefault()553     public int getSnapHeaderDefault() {
554         return mSnapHeaderDefault;
555     }
556 
getMigrationState()557     public int getMigrationState() {
558         return getSharedPreferences()
559                 .getInt(PreferenceKeys.MIGRATION_STATE, PreferenceKeys.MIGRATION_STATE_NONE);
560     }
561 
setMigrationState(final int state)562     public void setMigrationState(final int state) {
563         getEditor().putInt(PreferenceKeys.MIGRATION_STATE, state).apply();
564     }
565 
getRecentAccounts()566     public Set<String> getRecentAccounts() {
567         return getSharedPreferences().getStringSet(PreferenceKeys.RECENT_ACCOUNTS, null);
568     }
569 
setRecentAccounts(Set<String> recentAccounts)570     public void setRecentAccounts(Set<String> recentAccounts) {
571         getEditor().putStringSet(PreferenceKeys.RECENT_ACCOUNTS, recentAccounts).apply();
572     }
573 
574     /**
575      * Returns the minimum version number of the {@link com.android.mail.utils.HtmlSanitizer} which
576      * is trusted. If the version of the HtmlSanitizer does not meet or exceed this value,
577      * sanitization will be deemed untrustworthy and emails will be displayed in a sandbox that does
578      * not allow script execution.
579      */
getRequiredSanitizerVersionNumber()580     public int getRequiredSanitizerVersionNumber() {
581         return getSharedPreferences().getInt(PreferenceKeys.REQUIRED_SANITIZER_VERSION_NUMBER, 1);
582     }
583 
584     /**
585      * @param versionNumber the minimum version number of the
586      *      {@link com.android.mail.utils.HtmlSanitizer} which produces trusted output
587      */
setRequiredSanitizerVersionNumber(int versionNumber)588     public void setRequiredSanitizerVersionNumber(int versionNumber) {
589         getEditor().putInt(PreferenceKeys.REQUIRED_SANITIZER_VERSION_NUMBER, versionNumber).apply();
590     }
591 
592     /**
593      * Returns the latest time the number of accounts in the application was sent to Analyitcs.
594      * @return the datetime in epoch milliseconds.
595      */
getNbAccountsLatestReport()596     public long getNbAccountsLatestReport() {
597         return getSharedPreferences().getLong(PreferenceKeys.ANALYTICS_NB_ACCOUNT_LATEST_REPORT, 0);
598     }
599 
600     /**
601      * Set the latest time the number of accounts in the application was sent to Analytics.
602      */
setNbAccountsLatestReport(long timeMs)603     public void setNbAccountsLatestReport(long timeMs) {
604         getEditor().putLong(
605                 PreferenceKeys.ANALYTICS_NB_ACCOUNT_LATEST_REPORT, timeMs);
606     }
607 }
608