Home | History | Annotate | Download | only in preferences
      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 
    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
    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
    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
    203     protected boolean canBackup(final String key) {
    204         return PreferenceKeys.BACKUP_KEYS.contains(key);
    205     }
    206 
    207     @Override
    208     protected boolean hasMigrationCompleted() {
    209         return getSharedPreferences().getInt(PreferenceKeys.MIGRATED_VERSION, 0)
    210                 >= CURRENT_VERSION_NUMBER;
    211     }
    212 
    213     @Override
    214     protected void setMigrationComplete() {
    215         getEditor().putInt(PreferenceKeys.MIGRATED_VERSION, CURRENT_VERSION_NUMBER).commit();
    216     }
    217 
    218     public boolean isWidgetConfigured(int appWidgetId) {
    219         return getSharedPreferences().contains(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + appWidgetId);
    220     }
    221 
    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 
    231     public String getWidgetConfiguration(int appWidgetId) {
    232         return getSharedPreferences().getString(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + appWidgetId,
    233                 null);
    234     }
    235 
    236     private static String createWidgetPreferenceValue(Account account, String folderUri) {
    237         return account.uri.toString() + BaseWidgetProvider.ACCOUNT_FOLDER_PREFERENCE_SEPARATOR
    238                 + folderUri;
    239 
    240     }
    241 
    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" */
    250     public boolean getDefaultReplyAll() {
    251         return getSharedPreferences().getBoolean(PreferenceKeys.DEFAULT_REPLY_ALL, false);
    252     }
    253 
    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      */
    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      */
    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      */
    290     public boolean getIsConversationListSwipeEnabled() {
    291         final SharedPreferences sharedPreferences = getSharedPreferences();
    292         return sharedPreferences.getBoolean(PreferenceKeys.CONVERSATION_LIST_SWIPE, true);
    293     }
    294 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    359     public void setLongPressToSelectTipAlreadyShown() {
    360         getEditor().putInt(PreferenceKeys.LONG_PRESS_TO_SELECT_TIP_SHOWN, 1).apply();
    361         notifyBackupPreferenceChanged();
    362     }
    363 
    364     public void setSenderWhitelist(Set<String> addresses) {
    365         getEditor().putStringSet(PreferenceKeys.DISPLAY_IMAGES, addresses).apply();
    366         notifyBackupPreferenceChanged();
    367     }
    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 (at) bar.com")
    379      * @return whether we should show pictures for this sender
    380      */
    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 
    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 
    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 
    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 
    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 
    454     public void setShowSenderImages(boolean enable) {
    455         getEditor().putBoolean(PreferenceKeys.SHOW_SENDER_IMAGES, enable).apply();
    456         notifyBackupPreferenceChanged();
    457     }
    458 
    459     public boolean getShowSenderImages() {
    460         final SharedPreferences sharedPreferences = getSharedPreferences();
    461         return sharedPreferences.getBoolean(PreferenceKeys.SHOW_SENDER_IMAGES, true);
    462     }
    463 
    464     public int getNumOfDismissesForAutoSyncOff() {
    465         return getSharedPreferences().getInt(PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, 0);
    466     }
    467 
    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 
    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 
    482     public void setConfirmDelete(final boolean confirmDelete) {
    483         getEditor().putBoolean(PreferenceKeys.CONFIRM_DELETE, confirmDelete).apply();
    484         notifyBackupPreferenceChanged();
    485     }
    486 
    487     public boolean getConfirmDelete() {
    488         return getSharedPreferences().getBoolean(PreferenceKeys.CONFIRM_DELETE, false);
    489     }
    490 
    491     public void setConfirmArchive(final boolean confirmArchive) {
    492         getEditor().putBoolean(PreferenceKeys.CONFIRM_ARCHIVE, confirmArchive).apply();
    493         notifyBackupPreferenceChanged();
    494     }
    495 
    496     public boolean getConfirmArchive() {
    497         return getSharedPreferences().getBoolean(PreferenceKeys.CONFIRM_ARCHIVE, false);
    498     }
    499 
    500     public void setConfirmSend(final boolean confirmSend) {
    501         getEditor().putBoolean(PreferenceKeys.CONFIRM_SEND, confirmSend).apply();
    502         notifyBackupPreferenceChanged();
    503     }
    504 
    505     public boolean getConfirmSend() {
    506         return getSharedPreferences().getBoolean(PreferenceKeys.CONFIRM_SEND, false);
    507     }
    508 
    509     public void setAutoAdvanceMode(final int mode) {
    510         getEditor().putInt(PreferenceKeys.AUTO_ADVANCE_MODE, mode).apply();
    511         notifyBackupPreferenceChanged();
    512     }
    513 
    514     public int getAutoAdvanceMode() {
    515         return getSharedPreferences()
    516                 .getInt(PreferenceKeys.AUTO_ADVANCE_MODE, UIProvider.AutoAdvance.DEFAULT);
    517     }
    518 
    519     public void setConversationOverviewMode(final boolean overviewMode) {
    520         getEditor().putBoolean(PreferenceKeys.CONVERSATION_OVERVIEW_MODE, overviewMode).apply();
    521     }
    522 
    523     public boolean getConversationOverviewMode() {
    524         return getSharedPreferences()
    525                 .getBoolean(PreferenceKeys.CONVERSATION_OVERVIEW_MODE, true);
    526     }
    527 
    528     public boolean isConversationOverviewModeSet() {
    529         return getSharedPreferences().contains(PreferenceKeys.CONVERSATION_OVERVIEW_MODE);
    530     }
    531 
    532     public void setAlwaysLaunchGmailFromEmailTombstone(final boolean alwaysLaunchGmail) {
    533         getEditor()
    534                 .putBoolean(PreferenceKeys.ALWAYS_LAUNCH_GMAIL_FROM_EMAIL_TOMBSTONE,
    535                         alwaysLaunchGmail)
    536                 .apply();
    537     }
    538 
    539     public boolean getAlwaysLaunchGmailFromEmailTombstone() {
    540         return getSharedPreferences()
    541                 .getBoolean(PreferenceKeys.ALWAYS_LAUNCH_GMAIL_FROM_EMAIL_TOMBSTONE, false);
    542     }
    543 
    544     public void setSnapHeaderMode(final int snapHeaderMode) {
    545         getEditor().putInt(PreferenceKeys.SNAP_HEADER_MODE, snapHeaderMode).apply();
    546     }
    547 
    548     public int getSnapHeaderMode() {
    549         return getSharedPreferences()
    550                 .getInt(PreferenceKeys.SNAP_HEADER_MODE, mSnapHeaderDefault);
    551     }
    552 
    553     public int getSnapHeaderDefault() {
    554         return mSnapHeaderDefault;
    555     }
    556 
    557     public int getMigrationState() {
    558         return getSharedPreferences()
    559                 .getInt(PreferenceKeys.MIGRATION_STATE, PreferenceKeys.MIGRATION_STATE_NONE);
    560     }
    561 
    562     public void setMigrationState(final int state) {
    563         getEditor().putInt(PreferenceKeys.MIGRATION_STATE, state).apply();
    564     }
    565 
    566     public Set<String> getRecentAccounts() {
    567         return getSharedPreferences().getStringSet(PreferenceKeys.RECENT_ACCOUNTS, null);
    568     }
    569 
    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      */
    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      */
    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      */
    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      */
    603     public void setNbAccountsLatestReport(long timeMs) {
    604         getEditor().putLong(
    605                 PreferenceKeys.ANALYTICS_NB_ACCOUNT_LATEST_REPORT, timeMs);
    606     }
    607 }
    608