blob: c6146c85d0a7b9cb3e0a4ae312ad0e7a93c56137 [file] [log] [blame]
/*
* Copyright (C) 2012 Google Inc.
* Licensed to The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.mail.preferences;
import android.content.Context;
import android.content.SharedPreferences;
import com.android.mail.providers.Account;
import com.android.mail.providers.UIProvider;
import com.android.mail.utils.LogUtils;
import com.android.mail.widget.BaseWidgetProvider;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
/**
* A high-level API to store and retrieve unified mail preferences.
* <p>
* This will serve as an eventual replacement for Gmail's Persistence class.
*/
public final class MailPrefs extends VersionedPrefs {
public static final boolean SHOW_EXPERIMENTAL_PREFS = true;
private static final String PREFS_NAME = "UnifiedEmail";
private static MailPrefs sInstance;
public static final class PreferenceKeys {
private static final String MIGRATED_VERSION = "migrated-version";
public static final String WIDGET_ACCOUNT_PREFIX = "widget-account-";
/** Hidden preference to indicate what version a "What's New" dialog was last shown for. */
public static final String WHATS_NEW_LAST_SHOWN_VERSION = "whats-new-last-shown-version";
/**
* A boolean that, if <code>true</code>, means we should default all replies to "reply all"
*/
public static final String DEFAULT_REPLY_ALL = "default-reply-all";
/**
* A boolean that, if <code>true</code>, means we should allow conversation list swiping
*/
public static final String CONVERSATION_LIST_SWIPE = "conversation-list-swipe";
/** A string indicating the user's removal action preference. */
public static final String REMOVAL_ACTION = "removal-action";
/** Hidden preference used to cache the active notification set */
private static final String CACHED_ACTIVE_NOTIFICATION_SET =
"cache-active-notification-set";
/**
* A string indicating whether the conversation photo teaser has been previously
* shown and dismissed. This is the third version of it (thus the three at the end).
* Previous versions: "conversation-photo-teaser-shown"
* and "conversation-photo-teaser-shown-two".
*/
private static final String
CONVERSATION_PHOTO_TEASER_SHOWN = "conversation-photo-teaser-shown-three";
public static final String DISPLAY_IMAGES = "display_images";
public static final String DISPLAY_IMAGES_PATTERNS = "display_sender_images_patterns_set";
public static final String SHOW_SENDER_IMAGES = "conversation-list-sender-image";
public static final String SHOW_ATTACHMENT_PREVIEWS
= "conversation-list-attachment-previews";
public static final String
LONG_PRESS_TO_SELECT_TIP_SHOWN = "long-press-to-select-tip-shown";
public static final String EXPERIMENT_AP_PARALLAX_SPEED_ALTERNATIVE = "ap-parallax-speed";
public static final String EXPERIMENT_AP_PARALLAX_DIRECTION_ALTERNATIVE
= "ap-parallax-direction";
public static final String GLOBAL_SYNC_OFF_DISMISSES = "num-of-dismisses-auto-sync-off";
public static final String AIRPLANE_MODE_ON_DISMISSES = "num-of-dismisses-airplane-mode-on";
public static final ImmutableSet<String> BACKUP_KEYS =
new ImmutableSet.Builder<String>()
.add(DEFAULT_REPLY_ALL)
.add(CONVERSATION_LIST_SWIPE)
.add(REMOVAL_ACTION)
.add(DISPLAY_IMAGES)
.add(DISPLAY_IMAGES_PATTERNS)
.add(SHOW_SENDER_IMAGES)
.add(SHOW_ATTACHMENT_PREVIEWS)
.add(LONG_PRESS_TO_SELECT_TIP_SHOWN)
.build();
}
public static final class ConversationListSwipeActions {
public static final String ARCHIVE = "archive";
public static final String DELETE = "delete";
public static final String DISABLED = "disabled";
}
public static final class RemovalActions {
public static final String ARCHIVE = "archive";
public static final String DELETE = "delete";
public static final String ARCHIVE_AND_DELETE = "archive-and-delete";
}
public static MailPrefs get(Context c) {
if (sInstance == null) {
sInstance = new MailPrefs(c);
}
return sInstance;
}
private MailPrefs(Context c) {
super(c, PREFS_NAME);
}
@Override
protected void performUpgrade(final int oldVersion, final int newVersion) {
if (oldVersion > newVersion) {
throw new IllegalStateException(
"You appear to have downgraded your app. Please clear app data.");
} else if (oldVersion == newVersion) {
return;
}
}
@Override
protected boolean canBackup(final String key) {
return PreferenceKeys.BACKUP_KEYS.contains(key);
}
@Override
protected boolean hasMigrationCompleted() {
return getSharedPreferences().getInt(PreferenceKeys.MIGRATED_VERSION, 0)
>= CURRENT_VERSION_NUMBER;
}
@Override
protected void setMigrationComplete() {
getEditor().putInt(PreferenceKeys.MIGRATED_VERSION, CURRENT_VERSION_NUMBER).commit();
}
public boolean isWidgetConfigured(int appWidgetId) {
return getSharedPreferences().contains(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + appWidgetId);
}
public void configureWidget(int appWidgetId, Account account, final String folderUri) {
if (account == null) {
LogUtils.e(LOG_TAG, "Cannot configure widget with null account");
return;
}
getEditor().putString(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + appWidgetId,
createWidgetPreferenceValue(account, folderUri)).apply();
}
public String getWidgetConfiguration(int appWidgetId) {
return getSharedPreferences().getString(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + appWidgetId,
null);
}
private static String createWidgetPreferenceValue(Account account, String folderUri) {
return account.uri.toString() + BaseWidgetProvider.ACCOUNT_FOLDER_PREFERENCE_SEPARATOR
+ folderUri;
}
public void clearWidgets(int[] appWidgetIds) {
for (int id : appWidgetIds) {
getEditor().remove(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + id);
}
getEditor().apply();
}
/** If <code>true</code>, we should default all replies to "reply all" rather than "reply" */
public boolean getDefaultReplyAll() {
return getSharedPreferences().getBoolean(PreferenceKeys.DEFAULT_REPLY_ALL, false);
}
public void setDefaultReplyAll(final boolean replyAll) {
getEditor().putBoolean(PreferenceKeys.DEFAULT_REPLY_ALL, replyAll).apply();
notifyBackupPreferenceChanged();
}
/**
* Returns a string indicating the preferred removal action.
* Should be one of the {@link RemovalActions}.
*/
public String getRemovalAction(final boolean supportsArchive) {
final String defaultAction = supportsArchive
? RemovalActions.ARCHIVE_AND_DELETE : RemovalActions.DELETE;
final SharedPreferences sharedPreferences = getSharedPreferences();
return sharedPreferences.getString(PreferenceKeys.REMOVAL_ACTION, defaultAction);
}
/**
* Sets the removal action preference.
* @param removalAction The preferred {@link RemovalActions}.
*/
public void setRemovalAction(final String removalAction) {
getEditor().putString(PreferenceKeys.REMOVAL_ACTION, removalAction).apply();
notifyBackupPreferenceChanged();
}
/**
* Gets a boolean indicating whether conversation list swiping is enabled.
*/
public boolean getIsConversationListSwipeEnabled() {
final SharedPreferences sharedPreferences = getSharedPreferences();
return sharedPreferences.getBoolean(PreferenceKeys.CONVERSATION_LIST_SWIPE, true);
}
public void setConversationListSwipeEnabled(final boolean enabled) {
getEditor().putBoolean(PreferenceKeys.CONVERSATION_LIST_SWIPE, enabled).apply();
notifyBackupPreferenceChanged();
}
/**
* Gets the action to take (one of the values from {@link UIProvider.Swipe}) when an item in the
* conversation list is swiped.
*
* @param allowArchive <code>true</code> if Archive is an acceptable action (this will affect
* the default return value)
*/
public int getConversationListSwipeActionInteger(final boolean allowArchive) {
final boolean swipeEnabled = getIsConversationListSwipeEnabled();
final boolean archive = !RemovalActions.DELETE.equals(getRemovalAction(allowArchive));
if (swipeEnabled) {
return archive ? UIProvider.Swipe.ARCHIVE : UIProvider.Swipe.DELETE;
}
return UIProvider.Swipe.DISABLED;
}
/**
* Returns the previously cached notification set
*/
public Set<String> getActiveNotificationSet() {
return getSharedPreferences()
.getStringSet(PreferenceKeys.CACHED_ACTIVE_NOTIFICATION_SET, null);
}
/**
* Caches the current notification set.
*/
public void cacheActiveNotificationSet(final Set<String> notificationSet) {
getEditor().putStringSet(PreferenceKeys.CACHED_ACTIVE_NOTIFICATION_SET, notificationSet)
.apply();
}
/**
* Returns whether the teaser has been shown before
*/
public boolean isConversationPhotoTeaserAlreadyShown() {
return getSharedPreferences()
.getBoolean(PreferenceKeys.CONVERSATION_PHOTO_TEASER_SHOWN, false);
}
/**
* Notify that we have shown the teaser
*/
public void setConversationPhotoTeaserAlreadyShown() {
getEditor().putBoolean(PreferenceKeys.CONVERSATION_PHOTO_TEASER_SHOWN, true).apply();
}
/**
* Returns whether the tip has been shown before
*/
public boolean isLongPressToSelectTipAlreadyShown() {
// Using an int instead of boolean here in case we need to reshow the tip (don't have
// to use a new preference name).
return getSharedPreferences()
.getInt(PreferenceKeys.LONG_PRESS_TO_SELECT_TIP_SHOWN, 0) > 0;
}
public void setLongPressToSelectTipAlreadyShown() {
getEditor().putInt(PreferenceKeys.LONG_PRESS_TO_SELECT_TIP_SHOWN, 1).apply();
notifyBackupPreferenceChanged();
}
public void setSenderWhitelist(Set<String> addresses) {
getEditor().putStringSet(PreferenceKeys.DISPLAY_IMAGES, addresses).apply();
notifyBackupPreferenceChanged();
}
public void setSenderWhitelistPatterns(Set<String> patterns) {
getEditor().putStringSet(PreferenceKeys.DISPLAY_IMAGES_PATTERNS, patterns).apply();
notifyBackupPreferenceChanged();
}
/**
* Returns whether or not an email address is in the whitelist of senders to show images for.
* This method reads the entire whitelist, so if you have multiple emails to check, you should
* probably call getSenderWhitelist() and check membership yourself.
*
* @param sender raw email address ("foo@bar.com")
* @return whether we should show pictures for this sender
*/
public boolean getDisplayImagesFromSender(String sender) {
boolean displayImages = getSenderWhitelist().contains(sender);
if (!displayImages) {
final SharedPreferences sharedPreferences = getSharedPreferences();
// Check the saved email address patterns to determine if this pattern matches
final Set<String> defaultPatternSet = Collections.emptySet();
final Set<String> currentPatterns = sharedPreferences.getStringSet(
PreferenceKeys.DISPLAY_IMAGES_PATTERNS, defaultPatternSet);
for (String pattern : currentPatterns) {
displayImages = Pattern.compile(pattern).matcher(sender).matches();
if (displayImages) {
break;
}
}
}
return displayImages;
}
public void setDisplayImagesFromSender(String sender, List<Pattern> allowedPatterns) {
if (allowedPatterns != null) {
// Look at the list of patterns where we want to allow a particular class of
// email address
for (Pattern pattern : allowedPatterns) {
if (pattern.matcher(sender).matches()) {
// The specified email address matches one of the social network patterns.
// Save the pattern itself
final Set<String> currentPatterns = getSenderWhitelistPatterns();
final String patternRegex = pattern.pattern();
if (!currentPatterns.contains(patternRegex)) {
// Copy strings to a modifiable set
final Set<String> updatedPatterns = Sets.newHashSet(currentPatterns);
updatedPatterns.add(patternRegex);
setSenderWhitelistPatterns(updatedPatterns);
}
return;
}
}
}
final Set<String> whitelist = getSenderWhitelist();
if (!whitelist.contains(sender)) {
// Storing a JSONObject is slightly more nice in that maps are guaranteed to not have
// duplicate entries, but using a Set as intermediate representation guarantees this
// for us anyway. Also, using maps to represent sets forces you to pick values for
// them, and that's weird.
final Set<String> updatedList = Sets.newHashSet(whitelist);
updatedList.add(sender);
setSenderWhitelist(updatedList);
}
}
private Set<String> getSenderWhitelist() {
final SharedPreferences sharedPreferences = getSharedPreferences();
final Set<String> defaultAddressSet = Collections.emptySet();
return sharedPreferences.getStringSet(PreferenceKeys.DISPLAY_IMAGES, defaultAddressSet);
}
private Set<String> getSenderWhitelistPatterns() {
final SharedPreferences sharedPreferences = getSharedPreferences();
final Set<String> defaultPatternSet = Collections.emptySet();
return sharedPreferences.getStringSet(PreferenceKeys.DISPLAY_IMAGES_PATTERNS,
defaultPatternSet);
}
public void clearSenderWhiteList() {
final SharedPreferences.Editor editor = getEditor();
editor.putStringSet(PreferenceKeys.DISPLAY_IMAGES, Collections.EMPTY_SET);
editor.putStringSet(PreferenceKeys.DISPLAY_IMAGES_PATTERNS, Collections.EMPTY_SET);
editor.apply();
}
public void setShowSenderImages(boolean enable) {
getEditor().putBoolean(PreferenceKeys.SHOW_SENDER_IMAGES, enable).apply();
notifyBackupPreferenceChanged();
}
public boolean getShowSenderImages() {
final SharedPreferences sharedPreferences = getSharedPreferences();
return sharedPreferences.getBoolean(PreferenceKeys.SHOW_SENDER_IMAGES, true);
}
public void setShowAttachmentPreviews(final boolean enable) {
getEditor().putBoolean(PreferenceKeys.SHOW_ATTACHMENT_PREVIEWS, enable).apply();
notifyBackupPreferenceChanged();
}
public boolean getShowAttachmentPreviews() {
final SharedPreferences sharedPreferences = getSharedPreferences();
return sharedPreferences.getBoolean(PreferenceKeys.SHOW_ATTACHMENT_PREVIEWS, true);
}
public void setParallaxSpeedAlternative(final boolean alternative) {
getEditor().putBoolean(PreferenceKeys.EXPERIMENT_AP_PARALLAX_SPEED_ALTERNATIVE, alternative)
.apply();
notifyBackupPreferenceChanged();
}
public boolean getParallaxSpeedAlternative() {
final SharedPreferences sharedPreferences = getSharedPreferences();
return sharedPreferences
.getBoolean(PreferenceKeys.EXPERIMENT_AP_PARALLAX_SPEED_ALTERNATIVE, false);
}
public void setParallaxDirectionAlternative(final boolean alternative) {
getEditor().putBoolean(PreferenceKeys.EXPERIMENT_AP_PARALLAX_DIRECTION_ALTERNATIVE,
alternative).apply();
notifyBackupPreferenceChanged();
}
public boolean getParallaxDirectionAlternative() {
final SharedPreferences sharedPreferences = getSharedPreferences();
return sharedPreferences
.getBoolean(PreferenceKeys.EXPERIMENT_AP_PARALLAX_DIRECTION_ALTERNATIVE, false);
}
public int getNumOfDismissesForAutoSyncOff() {
return getSharedPreferences().getInt(PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, 0);
}
public void resetNumOfDismissesForAutoSyncOff() {
final int value = getSharedPreferences().getInt(
PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, 0);
if (value != 0) {
getEditor().putInt(PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, 0).apply();
}
}
public void incNumOfDismissesForAutoSyncOff() {
final int value = getSharedPreferences().getInt(
PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, 0);
getEditor().putInt(PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, value + 1).apply();
}
}