1/**
2 * Copyright (c) 2012, Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.mail.providers;
18
19import android.database.Cursor;
20import android.net.Uri;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.text.TextUtils;
24
25import com.android.mail.providers.UIProvider.AccountColumns.SettingsColumns;
26import com.android.mail.providers.UIProvider.AutoAdvance;
27import com.android.mail.providers.UIProvider.ConversationListIcon;
28import com.android.mail.providers.UIProvider.DefaultReplyBehavior;
29import com.android.mail.providers.UIProvider.SnapHeaderValue;
30import com.android.mail.providers.UIProvider.Swipe;
31import com.android.mail.utils.LogTag;
32import com.android.mail.utils.LogUtils;
33import com.android.mail.utils.Utils;
34import com.google.android.mail.common.base.Strings;
35import com.google.common.base.Objects;
36
37import org.json.JSONException;
38import org.json.JSONObject;
39
40import java.util.HashMap;
41import java.util.Map;
42
43/**
44 * Model to hold Settings for an account.
45 */
46public class Settings implements Parcelable {
47    private static final String LOG_TAG = LogTag.getLogTag();
48
49    static final Settings EMPTY_SETTINGS = new Settings();
50
51    // Max size for attachments (5 megs). Will be overridden by an account
52    // setting, if found.
53    private static final int DEFAULT_MAX_ATTACHMENT_SIZE = 5 * 1024 * 1024;
54
55    public static final int SWIPE_SETTING_ARCHIVE = 0;
56    public static final int SWIPE_SETTING_DELETE = 1;
57    public static final int SWIPE_SETTING_DISABLED = 2;
58
59    private static final int DEFAULT = SWIPE_SETTING_ARCHIVE;
60
61    public interface ShowImages {
62        public static final int ALWAYS = 0;
63        public static final int ASK_FIRST = 1;
64    }
65
66    public final String signature;
67    /**
68     * Auto advance setting for this account.
69     * Integer, one of {@link AutoAdvance#LIST}, {@link AutoAdvance#NEWER},
70     * {@link AutoAdvance#OLDER} or  {@link AutoAdvance#UNSET}
71     */
72    private final int mAutoAdvance;
73    private Integer mTransientAutoAdvance = null;
74    public final int snapHeaders;
75    public final int replyBehavior;
76    public final int convListIcon;
77    public final boolean confirmDelete;
78    public final boolean confirmArchive;
79    public final boolean confirmSend;
80    public final int conversationViewMode;
81    public final Uri defaultInbox;
82    /**
83     * The name of the default inbox: "Inbox" or "Priority Inbox", internationalized...
84     */
85    public final String defaultInboxName;
86    // If you find the need for more default Inbox information: ID or capabilities, then
87    // ask viki to replace the above two members with a single JSON object representing the default
88    // folder.  That should make all the information about the folder available without an
89    // explosion in the number of members.
90
91    public final boolean forceReplyFromDefault;
92    public final int maxAttachmentSize;
93    public final int swipe;
94    /** True if the yellow label indicating importance should be shown. */
95    public final boolean importanceMarkersEnabled;
96    /**
97     * true if personal level indicators should be shown:
98     * an arrow ( › ) by messages sent to my address (not a mailing list),
99     * and a double arrow ( » ) by messages sent only to me.
100     */
101    public final boolean showChevronsEnabled;
102    public final Uri setupIntentUri;
103    public final String veiledAddressPattern;
104    public final int showImages;
105    public final int welcomeTourShownVersion;
106
107    /**
108     * The {@link Uri} to use when moving a conversation to the inbox. May
109     * differ from {@link #defaultInbox}.
110     */
111    public final Uri moveToInbox;
112
113    /** Cached value of hashCode */
114    private int mHashCode;
115
116    /** Safe defaults to be used if some values are unspecified. */
117    private static final Settings sDefault = EMPTY_SETTINGS;
118
119    private Settings() {
120        signature = "";
121        mAutoAdvance = AutoAdvance.LIST;
122        snapHeaders = SnapHeaderValue.ALWAYS;
123        replyBehavior = DefaultReplyBehavior.REPLY;
124        convListIcon = ConversationListIcon.SENDER_IMAGE;
125        confirmDelete = false;
126        confirmArchive = false;
127        confirmSend = false;
128        defaultInbox = Uri.EMPTY;
129        defaultInboxName = "";
130        forceReplyFromDefault = false;
131        maxAttachmentSize = 0;
132        swipe = DEFAULT;
133        importanceMarkersEnabled = false;
134        showChevronsEnabled = false;
135        setupIntentUri = Uri.EMPTY;
136        conversationViewMode = UIProvider.ConversationViewMode.UNDEFINED;
137        veiledAddressPattern = null;
138        moveToInbox = Uri.EMPTY;
139        showImages = ShowImages.ASK_FIRST;
140        welcomeTourShownVersion = -1;
141    }
142
143    public Settings(Parcel inParcel) {
144        signature = inParcel.readString();
145        mAutoAdvance = inParcel.readInt();
146        snapHeaders = inParcel.readInt();
147        replyBehavior = inParcel.readInt();
148        convListIcon = inParcel.readInt();
149        confirmDelete = inParcel.readInt() != 0;
150        confirmArchive = inParcel.readInt() != 0;
151        confirmSend = inParcel.readInt() != 0;
152        defaultInbox = Utils.getValidUri(inParcel.readString());
153        defaultInboxName = inParcel.readString();
154        forceReplyFromDefault = inParcel.readInt() != 0;
155        maxAttachmentSize = inParcel.readInt();
156        swipe = inParcel.readInt();
157        importanceMarkersEnabled = inParcel.readInt() != 0;
158        showChevronsEnabled = inParcel.readInt() != 0;
159        setupIntentUri = Utils.getValidUri(inParcel.readString());
160        conversationViewMode = inParcel.readInt();
161        veiledAddressPattern = inParcel.readString();
162        moveToInbox = Utils.getValidUri(inParcel.readString());
163        showImages = inParcel.readInt();
164        welcomeTourShownVersion = inParcel.readInt();
165    }
166
167    public Settings(Cursor cursor) {
168        signature = Strings.nullToEmpty(
169                cursor.getString(cursor.getColumnIndex(SettingsColumns.SIGNATURE)));
170        mAutoAdvance = cursor.getInt(cursor.getColumnIndex(SettingsColumns.AUTO_ADVANCE));
171        snapHeaders = cursor.getInt(cursor.getColumnIndex(SettingsColumns.SNAP_HEADERS));
172        replyBehavior = cursor.getInt(cursor.getColumnIndex(SettingsColumns.REPLY_BEHAVIOR));
173        convListIcon = cursor.getInt(cursor.getColumnIndex(SettingsColumns.CONV_LIST_ICON));
174        confirmDelete = cursor.getInt(cursor.getColumnIndex(SettingsColumns.CONFIRM_DELETE)) != 0;
175        confirmArchive = cursor.getInt(cursor.getColumnIndex(SettingsColumns.CONFIRM_ARCHIVE)) != 0;
176        confirmSend = cursor.getInt(cursor.getColumnIndex(SettingsColumns.CONFIRM_SEND)) != 0;
177        defaultInbox = Utils.getValidUri(
178                cursor.getString(cursor.getColumnIndex(SettingsColumns.DEFAULT_INBOX)));
179        defaultInboxName = Strings.nullToEmpty(
180                cursor.getString(cursor.getColumnIndex(SettingsColumns.DEFAULT_INBOX_NAME)));
181        forceReplyFromDefault = cursor.getInt(
182                cursor.getColumnIndex(SettingsColumns.FORCE_REPLY_FROM_DEFAULT)) != 0;
183        maxAttachmentSize =
184                cursor.getInt(cursor.getColumnIndex(SettingsColumns.MAX_ATTACHMENT_SIZE));
185        swipe = cursor.getInt(cursor.getColumnIndex(SettingsColumns.SWIPE));
186        importanceMarkersEnabled = cursor.getInt(
187                cursor.getColumnIndex(SettingsColumns.IMPORTANCE_MARKERS_ENABLED)) != 0;
188        showChevronsEnabled = cursor.getInt(
189                cursor.getColumnIndex(SettingsColumns.SHOW_CHEVRONS_ENABLED)) != 0;
190        setupIntentUri = Utils.getValidUri(
191                cursor.getString(cursor.getColumnIndex(SettingsColumns.SETUP_INTENT_URI)));
192        conversationViewMode =
193                cursor.getInt(cursor.getColumnIndex(SettingsColumns.CONVERSATION_VIEW_MODE));
194        veiledAddressPattern = Strings.nullToEmpty(
195                cursor.getString(cursor.getColumnIndex(SettingsColumns.VEILED_ADDRESS_PATTERN)));
196        moveToInbox = Utils.getValidUri(
197                cursor.getString(cursor.getColumnIndex(SettingsColumns.MOVE_TO_INBOX)));
198        showImages = cursor.getInt(cursor.getColumnIndex(SettingsColumns.SHOW_IMAGES));
199        welcomeTourShownVersion = cursor.getInt(
200                cursor.getColumnIndex(SettingsColumns.WELCOME_TOUR_SHOWN_VERSION));
201    }
202
203    private Settings(JSONObject json) {
204        signature = json.optString(SettingsColumns.SIGNATURE, sDefault.signature);
205        mAutoAdvance = json.optInt(SettingsColumns.AUTO_ADVANCE, sDefault.getAutoAdvanceSetting());
206        snapHeaders = json.optInt(SettingsColumns.SNAP_HEADERS, sDefault.snapHeaders);
207        replyBehavior = json.optInt(SettingsColumns.REPLY_BEHAVIOR, sDefault.replyBehavior);
208        convListIcon = json.optInt(SettingsColumns.CONV_LIST_ICON, sDefault.convListIcon);
209        confirmDelete = json.optBoolean(SettingsColumns.CONFIRM_DELETE, sDefault.confirmDelete);
210        confirmArchive = json.optBoolean(SettingsColumns.CONFIRM_ARCHIVE, sDefault.confirmArchive);
211        confirmSend = json.optBoolean(SettingsColumns.CONFIRM_SEND, sDefault.confirmSend);
212        defaultInbox = Utils.getValidUri(json.optString(SettingsColumns.DEFAULT_INBOX));
213        defaultInboxName = json.optString(SettingsColumns.DEFAULT_INBOX_NAME,
214                sDefault.defaultInboxName);
215        forceReplyFromDefault = json.optBoolean(SettingsColumns.FORCE_REPLY_FROM_DEFAULT,
216                sDefault.forceReplyFromDefault);
217        maxAttachmentSize =
218                json.optInt(SettingsColumns.MAX_ATTACHMENT_SIZE, sDefault.maxAttachmentSize);
219        swipe = json.optInt(SettingsColumns.SWIPE, sDefault.swipe);
220        importanceMarkersEnabled = json.optBoolean(SettingsColumns.IMPORTANCE_MARKERS_ENABLED,
221                sDefault.importanceMarkersEnabled);
222        showChevronsEnabled = json.optBoolean(SettingsColumns.SHOW_CHEVRONS_ENABLED,
223                sDefault.showChevronsEnabled);
224        setupIntentUri = Utils.getValidUri(json.optString(SettingsColumns.SETUP_INTENT_URI));
225        conversationViewMode = json.optInt(SettingsColumns.CONVERSATION_VIEW_MODE,
226                UIProvider.ConversationViewMode.UNDEFINED);
227        veiledAddressPattern = json.optString(SettingsColumns.VEILED_ADDRESS_PATTERN, null);
228        moveToInbox = Utils.getValidUri(json.optString(SettingsColumns.MOVE_TO_INBOX));
229        showImages = json.optInt(SettingsColumns.SHOW_IMAGES, sDefault.showImages);
230        welcomeTourShownVersion = json.optInt(
231                SettingsColumns.WELCOME_TOUR_SHOWN_VERSION, sDefault.welcomeTourShownVersion);
232    }
233
234    /**
235     * Return a serialized String for these settings.
236     */
237    public synchronized String serialize() {
238        final JSONObject json = toJSON();
239        return json.toString();
240    }
241
242    private static Object getNonNull(Object candidate, Object fallback){
243        if (candidate == null)
244            return fallback;
245        return candidate;
246    }
247
248    /**
249     * Return a JSONObject for these settings.
250     */
251    public synchronized JSONObject toJSON() {
252        final JSONObject json = new JSONObject();
253        try {
254            json.put(SettingsColumns.SIGNATURE, getNonNull(signature, sDefault.signature));
255            json.put(SettingsColumns.AUTO_ADVANCE, getAutoAdvanceSetting());
256            json.put(SettingsColumns.SNAP_HEADERS, snapHeaders);
257            json.put(SettingsColumns.REPLY_BEHAVIOR, replyBehavior);
258            json.put(SettingsColumns.CONV_LIST_ICON, convListIcon);
259            json.put(SettingsColumns.CONFIRM_DELETE, confirmDelete);
260            json.put(SettingsColumns.CONFIRM_ARCHIVE, confirmArchive);
261            json.put(SettingsColumns.CONFIRM_SEND, confirmSend);
262            json.put(SettingsColumns.DEFAULT_INBOX,
263                    getNonNull(defaultInbox, sDefault.defaultInbox));
264            json.put(SettingsColumns.DEFAULT_INBOX_NAME,
265                    getNonNull(defaultInboxName, sDefault.defaultInboxName));
266            json.put(SettingsColumns.FORCE_REPLY_FROM_DEFAULT, forceReplyFromDefault);
267            json.put(SettingsColumns.MAX_ATTACHMENT_SIZE,  maxAttachmentSize);
268            json.put(SettingsColumns.SWIPE, swipe);
269            json.put(SettingsColumns.IMPORTANCE_MARKERS_ENABLED, importanceMarkersEnabled);
270            json.put(SettingsColumns.SHOW_CHEVRONS_ENABLED, showChevronsEnabled);
271            json.put(SettingsColumns.SETUP_INTENT_URI, setupIntentUri);
272            json.put(SettingsColumns.CONVERSATION_VIEW_MODE, conversationViewMode);
273            json.put(SettingsColumns.VEILED_ADDRESS_PATTERN, veiledAddressPattern);
274            json.put(SettingsColumns.MOVE_TO_INBOX,
275                    getNonNull(moveToInbox, sDefault.moveToInbox));
276            json.put(SettingsColumns.SHOW_IMAGES, showImages);
277            json.put(SettingsColumns.WELCOME_TOUR_SHOWN_VERSION, welcomeTourShownVersion);
278        } catch (JSONException e) {
279            LogUtils.wtf(LOG_TAG, e, "Could not serialize settings");
280        }
281        return json;
282    }
283
284    /**
285     * Creates a {@link Map} where the column name is the key and the value is the value.
286     * @param map map to insert values into or null
287     * @return the resulting map
288     */
289    public Map<String, Object> getValueMap(Map<String, Object> map) {
290        if (map == null) {
291            map = new HashMap<String, Object>();
292        }
293
294        map.put(SettingsColumns.SIGNATURE, signature);
295        map.put(SettingsColumns.AUTO_ADVANCE, getAutoAdvanceSetting());
296        map.put(SettingsColumns.SNAP_HEADERS, snapHeaders);
297        map.put(SettingsColumns.REPLY_BEHAVIOR, replyBehavior);
298        map.put(SettingsColumns.CONV_LIST_ICON, convListIcon);
299        map.put(SettingsColumns.CONFIRM_DELETE, confirmDelete ? 1 : 0);
300        map.put(SettingsColumns.CONFIRM_ARCHIVE, confirmArchive ? 1 : 0);
301        map.put(SettingsColumns.CONFIRM_SEND, confirmSend ? 1 : 0);
302        map.put(SettingsColumns.DEFAULT_INBOX, defaultInbox);
303        map.put(SettingsColumns.DEFAULT_INBOX_NAME, defaultInboxName);
304        map.put(SettingsColumns.FORCE_REPLY_FROM_DEFAULT, forceReplyFromDefault ? 1 : 0);
305        map.put(SettingsColumns.MAX_ATTACHMENT_SIZE, maxAttachmentSize);
306        map.put(SettingsColumns.SWIPE, swipe);
307        map.put(SettingsColumns.IMPORTANCE_MARKERS_ENABLED, importanceMarkersEnabled ? 1 : 0);
308        map.put(SettingsColumns.SHOW_CHEVRONS_ENABLED, showChevronsEnabled ? 1 : 0);
309        map.put(SettingsColumns.SETUP_INTENT_URI, setupIntentUri);
310        map.put(SettingsColumns.CONVERSATION_VIEW_MODE, conversationViewMode);
311        map.put(SettingsColumns.VEILED_ADDRESS_PATTERN, veiledAddressPattern);
312        map.put(SettingsColumns.MOVE_TO_INBOX, moveToInbox);
313        map.put(SettingsColumns.SHOW_IMAGES, showImages);
314        map.put(SettingsColumns.WELCOME_TOUR_SHOWN_VERSION, welcomeTourShownVersion);
315
316        return map;
317    }
318
319    /**
320     * Create a new instance of an Settings object using a JSONObject  instance created previously
321     * using {@link #toJSON()}. This returns null if the serialized instance was invalid or does
322     * not represent a valid account object.
323     *
324     * @param json Serialized object
325     * @return New settings object or null
326     */
327    public static Settings newInstance(JSONObject json) {
328        if (json == null) {
329            return null;
330        }
331        return new Settings(json);
332    }
333
334    @Override
335    public int describeContents() {
336        return 0;
337    }
338
339    @Override
340    public void writeToParcel(Parcel dest, int flags) {
341        dest.writeString((String) getNonNull(signature, sDefault.signature));
342        dest.writeInt(getAutoAdvanceSetting());
343        dest.writeInt(snapHeaders);
344        dest.writeInt(replyBehavior);
345        dest.writeInt(convListIcon);
346        dest.writeInt(confirmDelete ? 1 : 0);
347        dest.writeInt(confirmArchive? 1 : 0);
348        dest.writeInt(confirmSend? 1 : 0);
349        dest.writeString(getNonNull(defaultInbox, sDefault.defaultInbox).toString());
350        dest.writeString((String) getNonNull(defaultInboxName, sDefault.defaultInboxName));
351        dest.writeInt(forceReplyFromDefault ? 1 : 0);
352        dest.writeInt(maxAttachmentSize);
353        dest.writeInt(swipe);
354        dest.writeInt(importanceMarkersEnabled ? 1 : 0);
355        dest.writeInt(showChevronsEnabled ? 1 : 0);
356        dest.writeString(getNonNull(setupIntentUri, sDefault.setupIntentUri).toString());
357        dest.writeInt(conversationViewMode);
358        dest.writeString(veiledAddressPattern);
359        dest.writeString(getNonNull(moveToInbox, sDefault.moveToInbox).toString());
360        dest.writeInt(showImages);
361        dest.writeInt(welcomeTourShownVersion);
362    }
363
364    /**
365     * Returns the URI of the current account's default inbox if available, otherwise
366     * returns the empty URI {@link Uri#EMPTY}
367     * @param settings a settings object, possibly null.
368     * @return a valid default Inbox URI, or {@link Uri#EMPTY} if settings are null or no default
369     * is specified.
370     */
371    public static Uri getDefaultInboxUri(Settings settings) {
372        if (settings == null) {
373            return sDefault.defaultInbox;
374        }
375        return (Uri) getNonNull(settings.defaultInbox, sDefault.defaultInbox);
376    }
377
378    /**
379     * Gets the autoadvance setting for this object, which may have changed since the settings were
380     * initially loaded.
381     */
382    public int getAutoAdvanceSetting() {
383        if (mTransientAutoAdvance != null) {
384            return mTransientAutoAdvance;
385        }
386
387        return mAutoAdvance;
388    }
389
390    /**
391     * Sets the transient autoadvance setting, which will override the initial autoadvance setting.
392     */
393    public void setAutoAdvanceSetting(final int autoAdvance) {
394        mTransientAutoAdvance = autoAdvance;
395    }
396
397    /**
398     * @return true if {@link UIProvider.ConversationViewMode#OVERVIEW} mode is set. In the event
399     * that the setting is not yet set, fall back to
400     * {@link UIProvider.ConversationViewMode#DEFAULT}.
401     */
402    public boolean isOverviewMode() {
403        final int val = (conversationViewMode != UIProvider.ConversationViewMode.UNDEFINED) ?
404                conversationViewMode : UIProvider.ConversationViewMode.DEFAULT;
405        return (val == UIProvider.ConversationViewMode.OVERVIEW);
406    }
407
408    /**
409     * Return the swipe setting for the settings provided. It is safe to pass this method
410     * a null object. It always returns a valid {@link Swipe} setting.
411     * @return the auto advance setting, a constant from {@link Swipe}
412     */
413    public static int getSwipeSetting(Settings settings) {
414        return settings != null ? settings.swipe : sDefault.swipe;
415    }
416
417    @SuppressWarnings("hiding")
418    public static final Creator<Settings> CREATOR = new Creator<Settings>() {
419        @Override
420        public Settings createFromParcel(Parcel source) {
421            return new Settings(source);
422        }
423
424        @Override
425        public Settings[] newArray(int size) {
426            return new Settings[size];
427        }
428    };
429
430    /**
431     *  Get the maximum size in bytes for attachments.
432     */
433    public int getMaxAttachmentSize() {
434        return maxAttachmentSize <= 0 ? DEFAULT_MAX_ATTACHMENT_SIZE : maxAttachmentSize;
435    }
436
437    @Override
438    public boolean equals(final Object aThat) {
439        LogUtils.d(LOG_TAG, "Settings.equals(%s)", aThat);
440        if (this == aThat) {
441            return true;
442        }
443        if ((aThat == null) || (aThat.getClass() != this.getClass())) {
444            return false;
445        }
446        final Settings that = (Settings) aThat;
447        final boolean autoAdvanceEquals = mTransientAutoAdvance != null
448                ? mTransientAutoAdvance.equals(that.mTransientAutoAdvance)
449                : that.mTransientAutoAdvance == null;
450        return (TextUtils.equals(signature, that.signature)
451                && mAutoAdvance == that.mAutoAdvance
452                && autoAdvanceEquals
453                && snapHeaders == that.snapHeaders
454                && replyBehavior == that.replyBehavior
455                && convListIcon == that.convListIcon
456                && confirmDelete == that.confirmDelete
457                && confirmArchive == that.confirmArchive
458                && confirmSend == that.confirmSend
459                && Objects.equal(defaultInbox, that.defaultInbox)
460                // Not checking default Inbox name, since is is identical to the URI check above.
461                && forceReplyFromDefault == that.forceReplyFromDefault
462                && maxAttachmentSize == that.maxAttachmentSize
463                && swipe == that.swipe
464                && importanceMarkersEnabled == that.importanceMarkersEnabled
465                && showChevronsEnabled == that.showChevronsEnabled
466                && setupIntentUri == that.setupIntentUri
467                && conversationViewMode == that.conversationViewMode
468                && TextUtils.equals(veiledAddressPattern, that.veiledAddressPattern))
469                && Objects.equal(moveToInbox, that.moveToInbox)
470                && welcomeTourShownVersion == that.welcomeTourShownVersion;
471    }
472
473    @Override
474    public int hashCode() {
475        if (mHashCode == 0) {
476            mHashCode = super.hashCode()
477                    ^ Objects.hashCode(signature, mAutoAdvance, mTransientAutoAdvance,
478                    snapHeaders, replyBehavior, convListIcon, confirmDelete, confirmArchive,
479                    confirmSend, defaultInbox, forceReplyFromDefault, maxAttachmentSize, swipe,
480                    importanceMarkersEnabled, showChevronsEnabled, setupIntentUri,
481                    conversationViewMode, veiledAddressPattern, moveToInbox,
482                    welcomeTourShownVersion);
483        }
484        return mHashCode;
485    }
486}
487