• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
get(Context c)122     public static MailPrefs get(Context c) {
123         if (sInstance == null) {
124             sInstance = new MailPrefs(c);
125         }
126         return sInstance;
127     }
128 
MailPrefs(Context c)129     private MailPrefs(Context c) {
130         super(c, PREFS_NAME);
131     }
132 
133     @Override
performUpgrade(final int oldVersion, final int newVersion)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
canBackup(final String key)144     protected boolean canBackup(final String key) {
145         return PreferenceKeys.BACKUP_KEYS.contains(key);
146     }
147 
148     @Override
hasMigrationCompleted()149     protected boolean hasMigrationCompleted() {
150         return getSharedPreferences().getInt(PreferenceKeys.MIGRATED_VERSION, 0)
151                 >= CURRENT_VERSION_NUMBER;
152     }
153 
154     @Override
setMigrationComplete()155     protected void setMigrationComplete() {
156         getEditor().putInt(PreferenceKeys.MIGRATED_VERSION, CURRENT_VERSION_NUMBER).commit();
157     }
158 
isWidgetConfigured(int appWidgetId)159     public boolean isWidgetConfigured(int appWidgetId) {
160         return getSharedPreferences().contains(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + appWidgetId);
161     }
162 
configureWidget(int appWidgetId, Account account, final String folderUri)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 
getWidgetConfiguration(int appWidgetId)172     public String getWidgetConfiguration(int appWidgetId) {
173         return getSharedPreferences().getString(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + appWidgetId,
174                 null);
175     }
176 
createWidgetPreferenceValue(Account account, String folderUri)177     private static String createWidgetPreferenceValue(Account account, String folderUri) {
178         return account.uri.toString() + BaseWidgetProvider.ACCOUNT_FOLDER_PREFERENCE_SEPARATOR
179                 + folderUri;
180 
181     }
182 
clearWidgets(int[] appWidgetIds)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" */
getDefaultReplyAll()191     public boolean getDefaultReplyAll() {
192         return getSharedPreferences().getBoolean(PreferenceKeys.DEFAULT_REPLY_ALL, false);
193     }
194 
setDefaultReplyAll(final boolean replyAll)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      */
getRemovalAction(final boolean supportsArchive)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      */
setRemovalAction(final String removalAction)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      */
getIsConversationListSwipeEnabled()224     public boolean getIsConversationListSwipeEnabled() {
225         final SharedPreferences sharedPreferences = getSharedPreferences();
226         return sharedPreferences.getBoolean(PreferenceKeys.CONVERSATION_LIST_SWIPE, true);
227     }
228 
setConversationListSwipeEnabled(final boolean enabled)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      */
getConversationListSwipeActionInteger(final boolean allowArchive)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      */
getActiveNotificationSet()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      */
cacheActiveNotificationSet(final Set<String> notificationSet)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      */
isConversationPhotoTeaserAlreadyShown()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      */
setConversationPhotoTeaserAlreadyShown()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      */
isLongPressToSelectTipAlreadyShown()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 
setLongPressToSelectTipAlreadyShown()293     public void setLongPressToSelectTipAlreadyShown() {
294         getEditor().putInt(PreferenceKeys.LONG_PRESS_TO_SELECT_TIP_SHOWN, 1).apply();
295         notifyBackupPreferenceChanged();
296     }
297 
setSenderWhitelist(Set<String> addresses)298     public void setSenderWhitelist(Set<String> addresses) {
299         getEditor().putStringSet(PreferenceKeys.DISPLAY_IMAGES, addresses).apply();
300         notifyBackupPreferenceChanged();
301     }
setSenderWhitelistPatterns(Set<String> patterns)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@bar.com")
313      * @return whether we should show pictures for this sender
314      */
getDisplayImagesFromSender(String sender)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 
setDisplayImagesFromSender(String sender, List<Pattern> allowedPatterns)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 
getSenderWhitelist()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 
getSenderWhitelistPatterns()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 
clearSenderWhiteList()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 
setShowSenderImages(boolean enable)389     public void setShowSenderImages(boolean enable) {
390         getEditor().putBoolean(PreferenceKeys.SHOW_SENDER_IMAGES, enable).apply();
391         notifyBackupPreferenceChanged();
392     }
393 
getShowSenderImages()394     public boolean getShowSenderImages() {
395         final SharedPreferences sharedPreferences = getSharedPreferences();
396         return sharedPreferences.getBoolean(PreferenceKeys.SHOW_SENDER_IMAGES, true);
397     }
398 
setParallaxSpeedAlternative(final boolean alternative)399     public void setParallaxSpeedAlternative(final boolean alternative) {
400         getEditor().putBoolean(PreferenceKeys.EXPERIMENT_AP_PARALLAX_SPEED_ALTERNATIVE, alternative)
401                 .apply();
402         notifyBackupPreferenceChanged();
403     }
404 
getParallaxSpeedAlternative()405     public boolean getParallaxSpeedAlternative() {
406         final SharedPreferences sharedPreferences = getSharedPreferences();
407         return sharedPreferences
408                 .getBoolean(PreferenceKeys.EXPERIMENT_AP_PARALLAX_SPEED_ALTERNATIVE, false);
409     }
410 
setParallaxDirectionAlternative(final boolean alternative)411     public void setParallaxDirectionAlternative(final boolean alternative) {
412         getEditor().putBoolean(PreferenceKeys.EXPERIMENT_AP_PARALLAX_DIRECTION_ALTERNATIVE,
413                 alternative).apply();
414         notifyBackupPreferenceChanged();
415     }
416 
getParallaxDirectionAlternative()417     public boolean getParallaxDirectionAlternative() {
418         final SharedPreferences sharedPreferences = getSharedPreferences();
419         return sharedPreferences
420                 .getBoolean(PreferenceKeys.EXPERIMENT_AP_PARALLAX_DIRECTION_ALTERNATIVE, false);
421     }
422 
getNumOfDismissesForAutoSyncOff()423     public int getNumOfDismissesForAutoSyncOff() {
424         return getSharedPreferences().getInt(PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, 0);
425     }
426 
resetNumOfDismissesForAutoSyncOff()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 
incNumOfDismissesForAutoSyncOff()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