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 
     23 import com.android.mail.providers.Account;
     24 import com.android.mail.providers.UIProvider;
     25 import com.android.mail.utils.LogUtils;
     26 import com.android.mail.widget.BaseWidgetProvider;
     27 
     28 import com.google.common.collect.ImmutableSet;
     29 import com.google.common.collect.Sets;
     30 
     31 import java.util.Collections;
     32 import java.util.List;
     33 import java.util.Set;
     34 import java.util.regex.Pattern;
     35 
     36 /**
     37  * A high-level API to store and retrieve unified mail preferences.
     38  * <p>
     39  * This will serve as an eventual replacement for Gmail's Persistence class.
     40  */
     41 public final class MailPrefs extends VersionedPrefs {
     42 
     43     public static final boolean SHOW_EXPERIMENTAL_PREFS = false;
     44 
     45     private static final String PREFS_NAME = "UnifiedEmail";
     46 
     47     private static MailPrefs sInstance;
     48 
     49     public static final class PreferenceKeys {
     50         private static final String MIGRATED_VERSION = "migrated-version";
     51 
     52         public static final String WIDGET_ACCOUNT_PREFIX = "widget-account-";
     53 
     54         /** Hidden preference to indicate what version a "What's New" dialog was last shown for. */
     55         public static final String WHATS_NEW_LAST_SHOWN_VERSION = "whats-new-last-shown-version";
     56 
     57         /**
     58          * A boolean that, if <code>true</code>, means we should default all replies to "reply all"
     59          */
     60         public static final String DEFAULT_REPLY_ALL = "default-reply-all";
     61         /**
     62          * A boolean that, if <code>true</code>, means we should allow conversation list swiping
     63          */
     64         public static final String CONVERSATION_LIST_SWIPE = "conversation-list-swipe";
     65 
     66         /** A string indicating the user's removal action preference. */
     67         public static final String REMOVAL_ACTION = "removal-action";
     68 
     69         /** Hidden preference used to cache the active notification set */
     70         private static final String CACHED_ACTIVE_NOTIFICATION_SET =
     71                 "cache-active-notification-set";
     72 
     73         /**
     74          * A string indicating whether the conversation photo teaser has been previously
     75          * shown and dismissed. This is the third version of it (thus the three at the end).
     76          * Previous versions: "conversation-photo-teaser-shown"
     77          * and "conversation-photo-teaser-shown-two".
     78          */
     79         private static final String
     80                 CONVERSATION_PHOTO_TEASER_SHOWN = "conversation-photo-teaser-shown-three";
     81 
     82         public static final String DISPLAY_IMAGES = "display_images";
     83         public static final String DISPLAY_IMAGES_PATTERNS = "display_sender_images_patterns_set";
     84 
     85 
     86         public static final String SHOW_SENDER_IMAGES = "conversation-list-sender-image";
     87 
     88         public static final String
     89                 LONG_PRESS_TO_SELECT_TIP_SHOWN = "long-press-to-select-tip-shown";
     90 
     91         public static final String EXPERIMENT_AP_PARALLAX_SPEED_ALTERNATIVE = "ap-parallax-speed";
     92         public static final String EXPERIMENT_AP_PARALLAX_DIRECTION_ALTERNATIVE
     93                 = "ap-parallax-direction";
     94 
     95         public static final String GLOBAL_SYNC_OFF_DISMISSES = "num-of-dismisses-auto-sync-off";
     96         public static final String AIRPLANE_MODE_ON_DISMISSES = "num-of-dismisses-airplane-mode-on";
     97 
     98         public static final ImmutableSet<String> BACKUP_KEYS =
     99                 new ImmutableSet.Builder<String>()
    100                 .add(DEFAULT_REPLY_ALL)
    101                 .add(CONVERSATION_LIST_SWIPE)
    102                 .add(REMOVAL_ACTION)
    103                 .add(DISPLAY_IMAGES)
    104                 .add(DISPLAY_IMAGES_PATTERNS)
    105                 .add(SHOW_SENDER_IMAGES)
    106                 .add(LONG_PRESS_TO_SELECT_TIP_SHOWN)
    107                 .build();
    108     }
    109 
    110     public static final class ConversationListSwipeActions {
    111         public static final String ARCHIVE = "archive";
    112         public static final String DELETE = "delete";
    113         public static final String DISABLED = "disabled";
    114     }
    115 
    116     public static final class RemovalActions {
    117         public static final String ARCHIVE = "archive";
    118         public static final String DELETE = "delete";
    119         public static final String ARCHIVE_AND_DELETE = "archive-and-delete";
    120     }
    121 
    122     public static MailPrefs get(Context c) {
    123         if (sInstance == null) {
    124             sInstance = new MailPrefs(c);
    125         }
    126         return sInstance;
    127     }
    128 
    129     private MailPrefs(Context c) {
    130         super(c, PREFS_NAME);
    131     }
    132 
    133     @Override
    134     protected void performUpgrade(final int oldVersion, final int newVersion) {
    135         if (oldVersion > newVersion) {
    136             throw new IllegalStateException(
    137                     "You appear to have downgraded your app. Please clear app data.");
    138         } else if (oldVersion == newVersion) {
    139             return;
    140         }
    141     }
    142 
    143     @Override
    144     protected boolean canBackup(final String key) {
    145         return PreferenceKeys.BACKUP_KEYS.contains(key);
    146     }
    147 
    148     @Override
    149     protected boolean hasMigrationCompleted() {
    150         return getSharedPreferences().getInt(PreferenceKeys.MIGRATED_VERSION, 0)
    151                 >= CURRENT_VERSION_NUMBER;
    152     }
    153 
    154     @Override
    155     protected void setMigrationComplete() {
    156         getEditor().putInt(PreferenceKeys.MIGRATED_VERSION, CURRENT_VERSION_NUMBER).commit();
    157     }
    158 
    159     public boolean isWidgetConfigured(int appWidgetId) {
    160         return getSharedPreferences().contains(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + appWidgetId);
    161     }
    162 
    163     public void configureWidget(int appWidgetId, Account account, final String folderUri) {
    164         if (account == null) {
    165             LogUtils.e(LOG_TAG, "Cannot configure widget with null account");
    166             return;
    167         }
    168         getEditor().putString(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + appWidgetId,
    169                 createWidgetPreferenceValue(account, folderUri)).apply();
    170     }
    171 
    172     public String getWidgetConfiguration(int appWidgetId) {
    173         return getSharedPreferences().getString(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + appWidgetId,
    174                 null);
    175     }
    176 
    177     private static String createWidgetPreferenceValue(Account account, String folderUri) {
    178         return account.uri.toString() + BaseWidgetProvider.ACCOUNT_FOLDER_PREFERENCE_SEPARATOR
    179                 + folderUri;
    180 
    181     }
    182 
    183     public void clearWidgets(int[] appWidgetIds) {
    184         for (int id : appWidgetIds) {
    185             getEditor().remove(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + id);
    186         }
    187         getEditor().apply();
    188     }
    189 
    190     /** If <code>true</code>, we should default all replies to "reply all" rather than "reply" */
    191     public boolean getDefaultReplyAll() {
    192         return getSharedPreferences().getBoolean(PreferenceKeys.DEFAULT_REPLY_ALL, false);
    193     }
    194 
    195     public void setDefaultReplyAll(final boolean replyAll) {
    196         getEditor().putBoolean(PreferenceKeys.DEFAULT_REPLY_ALL, replyAll).apply();
    197         notifyBackupPreferenceChanged();
    198     }
    199 
    200     /**
    201      * Returns a string indicating the preferred removal action.
    202      * Should be one of the {@link RemovalActions}.
    203      */
    204     public String getRemovalAction(final boolean supportsArchive) {
    205         final String defaultAction = supportsArchive
    206                 ? RemovalActions.ARCHIVE_AND_DELETE : RemovalActions.DELETE;
    207 
    208         final SharedPreferences sharedPreferences = getSharedPreferences();
    209         return sharedPreferences.getString(PreferenceKeys.REMOVAL_ACTION, defaultAction);
    210     }
    211 
    212     /**
    213      * Sets the removal action preference.
    214      * @param removalAction The preferred {@link RemovalActions}.
    215      */
    216     public void setRemovalAction(final String removalAction) {
    217         getEditor().putString(PreferenceKeys.REMOVAL_ACTION, removalAction).apply();
    218         notifyBackupPreferenceChanged();
    219     }
    220 
    221     /**
    222      * Gets a boolean indicating whether conversation list swiping is enabled.
    223      */
    224     public boolean getIsConversationListSwipeEnabled() {
    225         final SharedPreferences sharedPreferences = getSharedPreferences();
    226         return sharedPreferences.getBoolean(PreferenceKeys.CONVERSATION_LIST_SWIPE, true);
    227     }
    228 
    229     public void setConversationListSwipeEnabled(final boolean enabled) {
    230         getEditor().putBoolean(PreferenceKeys.CONVERSATION_LIST_SWIPE, enabled).apply();
    231         notifyBackupPreferenceChanged();
    232     }
    233 
    234     /**
    235      * Gets the action to take (one of the values from {@link UIProvider.Swipe}) when an item in the
    236      * conversation list is swiped.
    237      *
    238      * @param allowArchive <code>true</code> if Archive is an acceptable action (this will affect
    239      *        the default return value)
    240      */
    241     public int getConversationListSwipeActionInteger(final boolean allowArchive) {
    242         final boolean swipeEnabled = getIsConversationListSwipeEnabled();
    243         final boolean archive = !RemovalActions.DELETE.equals(getRemovalAction(allowArchive));
    244 
    245         if (swipeEnabled) {
    246             return archive ? UIProvider.Swipe.ARCHIVE : UIProvider.Swipe.DELETE;
    247         }
    248 
    249         return UIProvider.Swipe.DISABLED;
    250     }
    251 
    252     /**
    253      * Returns the previously cached notification set
    254      */
    255     public Set<String> getActiveNotificationSet() {
    256         return getSharedPreferences()
    257                 .getStringSet(PreferenceKeys.CACHED_ACTIVE_NOTIFICATION_SET, null);
    258     }
    259 
    260     /**
    261      * Caches the current notification set.
    262      */
    263     public void cacheActiveNotificationSet(final Set<String> notificationSet) {
    264         getEditor().putStringSet(PreferenceKeys.CACHED_ACTIVE_NOTIFICATION_SET, notificationSet)
    265                 .apply();
    266     }
    267 
    268     /**
    269      * Returns whether the teaser has been shown before
    270      */
    271     public boolean isConversationPhotoTeaserAlreadyShown() {
    272         return getSharedPreferences()
    273                 .getBoolean(PreferenceKeys.CONVERSATION_PHOTO_TEASER_SHOWN, false);
    274     }
    275 
    276     /**
    277      * Notify that we have shown the teaser
    278      */
    279     public void setConversationPhotoTeaserAlreadyShown() {
    280         getEditor().putBoolean(PreferenceKeys.CONVERSATION_PHOTO_TEASER_SHOWN, true).apply();
    281     }
    282 
    283     /**
    284      * Returns whether the tip has been shown before
    285      */
    286     public boolean isLongPressToSelectTipAlreadyShown() {
    287         // Using an int instead of boolean here in case we need to reshow the tip (don't have
    288         // to use a new preference name).
    289         return getSharedPreferences()
    290                 .getInt(PreferenceKeys.LONG_PRESS_TO_SELECT_TIP_SHOWN, 0) > 0;
    291     }
    292 
    293     public void setLongPressToSelectTipAlreadyShown() {
    294         getEditor().putInt(PreferenceKeys.LONG_PRESS_TO_SELECT_TIP_SHOWN, 1).apply();
    295         notifyBackupPreferenceChanged();
    296     }
    297 
    298     public void setSenderWhitelist(Set<String> addresses) {
    299         getEditor().putStringSet(PreferenceKeys.DISPLAY_IMAGES, addresses).apply();
    300         notifyBackupPreferenceChanged();
    301     }
    302     public void setSenderWhitelistPatterns(Set<String> patterns) {
    303         getEditor().putStringSet(PreferenceKeys.DISPLAY_IMAGES_PATTERNS, patterns).apply();
    304         notifyBackupPreferenceChanged();
    305     }
    306 
    307     /**
    308      * Returns whether or not an email address is in the whitelist of senders to show images for.
    309      * This method reads the entire whitelist, so if you have multiple emails to check, you should
    310      * probably call getSenderWhitelist() and check membership yourself.
    311      *
    312      * @param sender raw email address ("foo (at) bar.com")
    313      * @return whether we should show pictures for this sender
    314      */
    315     public boolean getDisplayImagesFromSender(String sender) {
    316         boolean displayImages = getSenderWhitelist().contains(sender);
    317         if (!displayImages) {
    318             final SharedPreferences sharedPreferences = getSharedPreferences();
    319             // Check the saved email address patterns to determine if this pattern matches
    320             final Set<String> defaultPatternSet = Collections.emptySet();
    321             final Set<String> currentPatterns = sharedPreferences.getStringSet(
    322                         PreferenceKeys.DISPLAY_IMAGES_PATTERNS, defaultPatternSet);
    323             for (String pattern : currentPatterns) {
    324                 displayImages = Pattern.compile(pattern).matcher(sender).matches();
    325                 if (displayImages) {
    326                     break;
    327                 }
    328             }
    329         }
    330 
    331         return displayImages;
    332     }
    333 
    334 
    335     public void setDisplayImagesFromSender(String sender, List<Pattern> allowedPatterns) {
    336         if (allowedPatterns != null) {
    337             // Look at the list of patterns where we want to allow a particular class of
    338             // email address
    339             for (Pattern pattern : allowedPatterns) {
    340                 if (pattern.matcher(sender).matches()) {
    341                     // The specified email address matches one of the social network patterns.
    342                     // Save the pattern itself
    343                     final Set<String> currentPatterns = getSenderWhitelistPatterns();
    344                     final String patternRegex = pattern.pattern();
    345                     if (!currentPatterns.contains(patternRegex)) {
    346                         // Copy strings to a modifiable set
    347                         final Set<String> updatedPatterns = Sets.newHashSet(currentPatterns);
    348                         updatedPatterns.add(patternRegex);
    349                         setSenderWhitelistPatterns(updatedPatterns);
    350                     }
    351                     return;
    352                 }
    353             }
    354         }
    355         final Set<String> whitelist = getSenderWhitelist();
    356         if (!whitelist.contains(sender)) {
    357             // Storing a JSONObject is slightly more nice in that maps are guaranteed to not have
    358             // duplicate entries, but using a Set as intermediate representation guarantees this
    359             // for us anyway. Also, using maps to represent sets forces you to pick values for
    360             // them, and that's weird.
    361             final Set<String> updatedList = Sets.newHashSet(whitelist);
    362             updatedList.add(sender);
    363             setSenderWhitelist(updatedList);
    364         }
    365     }
    366 
    367     private Set<String> getSenderWhitelist() {
    368         final SharedPreferences sharedPreferences = getSharedPreferences();
    369         final Set<String> defaultAddressSet = Collections.emptySet();
    370         return sharedPreferences.getStringSet(PreferenceKeys.DISPLAY_IMAGES, defaultAddressSet);
    371     }
    372 
    373 
    374     private Set<String> getSenderWhitelistPatterns() {
    375         final SharedPreferences sharedPreferences = getSharedPreferences();
    376         final Set<String> defaultPatternSet = Collections.emptySet();
    377         return sharedPreferences.getStringSet(PreferenceKeys.DISPLAY_IMAGES_PATTERNS,
    378                 defaultPatternSet);
    379     }
    380 
    381     public void clearSenderWhiteList() {
    382         final SharedPreferences.Editor editor = getEditor();
    383         editor.putStringSet(PreferenceKeys.DISPLAY_IMAGES, Collections.EMPTY_SET);
    384         editor.putStringSet(PreferenceKeys.DISPLAY_IMAGES_PATTERNS, Collections.EMPTY_SET);
    385         editor.apply();
    386     }
    387 
    388 
    389     public void setShowSenderImages(boolean enable) {
    390         getEditor().putBoolean(PreferenceKeys.SHOW_SENDER_IMAGES, enable).apply();
    391         notifyBackupPreferenceChanged();
    392     }
    393 
    394     public boolean getShowSenderImages() {
    395         final SharedPreferences sharedPreferences = getSharedPreferences();
    396         return sharedPreferences.getBoolean(PreferenceKeys.SHOW_SENDER_IMAGES, true);
    397     }
    398 
    399     public void setParallaxSpeedAlternative(final boolean alternative) {
    400         getEditor().putBoolean(PreferenceKeys.EXPERIMENT_AP_PARALLAX_SPEED_ALTERNATIVE, alternative)
    401                 .apply();
    402         notifyBackupPreferenceChanged();
    403     }
    404 
    405     public boolean getParallaxSpeedAlternative() {
    406         final SharedPreferences sharedPreferences = getSharedPreferences();
    407         return sharedPreferences
    408                 .getBoolean(PreferenceKeys.EXPERIMENT_AP_PARALLAX_SPEED_ALTERNATIVE, false);
    409     }
    410 
    411     public void setParallaxDirectionAlternative(final boolean alternative) {
    412         getEditor().putBoolean(PreferenceKeys.EXPERIMENT_AP_PARALLAX_DIRECTION_ALTERNATIVE,
    413                 alternative).apply();
    414         notifyBackupPreferenceChanged();
    415     }
    416 
    417     public boolean getParallaxDirectionAlternative() {
    418         final SharedPreferences sharedPreferences = getSharedPreferences();
    419         return sharedPreferences
    420                 .getBoolean(PreferenceKeys.EXPERIMENT_AP_PARALLAX_DIRECTION_ALTERNATIVE, false);
    421     }
    422 
    423     public int getNumOfDismissesForAutoSyncOff() {
    424         return getSharedPreferences().getInt(PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, 0);
    425     }
    426 
    427     public void resetNumOfDismissesForAutoSyncOff() {
    428         final int value = getSharedPreferences().getInt(
    429                 PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, 0);
    430         if (value != 0) {
    431             getEditor().putInt(PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, 0).apply();
    432         }
    433     }
    434 
    435     public void incNumOfDismissesForAutoSyncOff() {
    436         final int value = getSharedPreferences().getInt(
    437                 PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, 0);
    438         getEditor().putInt(PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, value + 1).apply();
    439     }
    440 }
    441