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.MessageTextSize;
30import com.android.mail.providers.UIProvider.SnapHeaderValue;
31import com.android.mail.providers.UIProvider.Swipe;
32import com.android.mail.utils.LogTag;
33import com.android.mail.utils.LogUtils;
34import com.android.mail.utils.Utils;
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 final String signature;
62    /**
63     * Auto advance setting for this account.
64     * Integer, one of {@link AutoAdvance#LIST}, {@link AutoAdvance#NEWER},
65     * {@link AutoAdvance#OLDER} or  {@link AutoAdvance#UNSET}
66     */
67    private final int mAutoAdvance;
68    private Integer mTransientAutoAdvance = null;
69    public final int messageTextSize;
70    public final int snapHeaders;
71    public final int replyBehavior;
72    public final int convListIcon;
73    public final boolean  convListAttachmentPreviews;
74    public final boolean confirmDelete;
75    public final boolean confirmArchive;
76    public final boolean confirmSend;
77    public final int conversationViewMode;
78    public final Uri defaultInbox;
79    /**
80     * The name of the default inbox: "Inbox" or "Priority Inbox", internationalized...
81     */
82    public final String defaultInboxName;
83    // If you find the need for more default Inbox information: ID or capabilities, then
84    // ask viki to replace the above two members with a single JSON object representing the default
85    // folder.  That should make all the information about the folder available without an
86    // explosion in the number of members.
87
88    public final boolean forceReplyFromDefault;
89    public final int maxAttachmentSize;
90    public final int swipe;
91    /** True if arrows on the priority inbox are enabled. */
92    public final boolean priorityArrowsEnabled;
93    public final Uri setupIntentUri;
94    public final String veiledAddressPattern;
95
96    /**
97     * The {@link Uri} to use when moving a conversation to the inbox. May
98     * differ from {@link #defaultInbox}.
99     */
100    public final Uri moveToInbox;
101
102    /** Cached value of hashCode */
103    private int mHashCode;
104
105    /** Safe defaults to be used if some values are unspecified. */
106    private static final Settings sDefault = EMPTY_SETTINGS;
107
108    private Settings() {
109        signature = "";
110        mAutoAdvance = AutoAdvance.LIST;
111        messageTextSize = MessageTextSize.NORMAL;
112        snapHeaders = SnapHeaderValue.ALWAYS;
113        replyBehavior = DefaultReplyBehavior.REPLY;
114        convListIcon = ConversationListIcon.SENDER_IMAGE;
115        convListAttachmentPreviews = true;
116        confirmDelete = false;
117        confirmArchive = false;
118        confirmSend = false;
119        defaultInbox = Uri.EMPTY;
120        defaultInboxName = "";
121        forceReplyFromDefault = false;
122        maxAttachmentSize = 0;
123        swipe = DEFAULT;
124        priorityArrowsEnabled = false;
125        setupIntentUri = Uri.EMPTY;
126        conversationViewMode = UIProvider.ConversationViewMode.UNDEFINED;
127        veiledAddressPattern = null;
128        moveToInbox = Uri.EMPTY;
129    }
130
131    public Settings(Parcel inParcel) {
132        signature = inParcel.readString();
133        mAutoAdvance = inParcel.readInt();
134        messageTextSize = inParcel.readInt();
135        snapHeaders = inParcel.readInt();
136        replyBehavior = inParcel.readInt();
137        convListIcon = inParcel.readInt();
138        convListAttachmentPreviews = inParcel.readInt() != 0;
139        confirmDelete = inParcel.readInt() != 0;
140        confirmArchive = inParcel.readInt() != 0;
141        confirmSend = inParcel.readInt() != 0;
142        defaultInbox = Utils.getValidUri(inParcel.readString());
143        defaultInboxName = inParcel.readString();
144        forceReplyFromDefault = inParcel.readInt() != 0;
145        maxAttachmentSize = inParcel.readInt();
146        swipe = inParcel.readInt();
147        priorityArrowsEnabled = inParcel.readInt() != 0;
148        setupIntentUri = Utils.getValidUri(inParcel.readString());
149        conversationViewMode = inParcel.readInt();
150        veiledAddressPattern = inParcel.readString();
151        moveToInbox = Utils.getValidUri(inParcel.readString());
152    }
153
154    public Settings(Cursor cursor) {
155        signature = cursor.getString(cursor.getColumnIndex(SettingsColumns.SIGNATURE));
156        mAutoAdvance = cursor.getInt(cursor.getColumnIndex(SettingsColumns.AUTO_ADVANCE));
157        messageTextSize = cursor.getInt(cursor.getColumnIndex(SettingsColumns.MESSAGE_TEXT_SIZE));
158        snapHeaders = cursor.getInt(cursor.getColumnIndex(SettingsColumns.SNAP_HEADERS));
159        replyBehavior = cursor.getInt(cursor.getColumnIndex(SettingsColumns.REPLY_BEHAVIOR));
160        convListIcon = cursor.getInt(cursor.getColumnIndex(SettingsColumns.CONV_LIST_ICON));
161        convListAttachmentPreviews = cursor
162                .getInt(cursor.getColumnIndex(SettingsColumns.CONV_LIST_ATTACHMENT_PREVIEWS)) != 0;
163        confirmDelete = cursor.getInt(cursor.getColumnIndex(SettingsColumns.CONFIRM_DELETE)) != 0;
164        confirmArchive = cursor.getInt(cursor.getColumnIndex(SettingsColumns.CONFIRM_ARCHIVE)) != 0;
165        confirmSend = cursor.getInt(cursor.getColumnIndex(SettingsColumns.CONFIRM_SEND)) != 0;
166        defaultInbox = Utils.getValidUri(
167                cursor.getString(cursor.getColumnIndex(SettingsColumns.DEFAULT_INBOX)));
168        defaultInboxName =
169                cursor.getString(cursor.getColumnIndex(SettingsColumns.DEFAULT_INBOX_NAME));
170        forceReplyFromDefault = cursor.getInt(
171                cursor.getColumnIndex(SettingsColumns.FORCE_REPLY_FROM_DEFAULT)) != 0;
172        maxAttachmentSize =
173                cursor.getInt(cursor.getColumnIndex(SettingsColumns.MAX_ATTACHMENT_SIZE));
174        swipe = cursor.getInt(cursor.getColumnIndex(SettingsColumns.SWIPE));
175        priorityArrowsEnabled = cursor.getInt(
176                cursor.getColumnIndex(SettingsColumns.PRIORITY_ARROWS_ENABLED)) != 0;
177        setupIntentUri = Utils.getValidUri(
178                cursor.getString(cursor.getColumnIndex(SettingsColumns.SETUP_INTENT_URI)));
179        conversationViewMode =
180                cursor.getInt(cursor.getColumnIndex(SettingsColumns.CONVERSATION_VIEW_MODE));
181        veiledAddressPattern =
182                cursor.getString(cursor.getColumnIndex(SettingsColumns.VEILED_ADDRESS_PATTERN));
183        moveToInbox = Utils.getValidUri(
184                cursor.getString(cursor.getColumnIndex(SettingsColumns.MOVE_TO_INBOX)));
185    }
186
187    private Settings(JSONObject json) {
188        signature = json.optString(SettingsColumns.SIGNATURE, sDefault.signature);
189        mAutoAdvance = json.optInt(SettingsColumns.AUTO_ADVANCE, sDefault.getAutoAdvanceSetting());
190        messageTextSize = json.optInt(SettingsColumns.MESSAGE_TEXT_SIZE, sDefault.messageTextSize);
191        snapHeaders = json.optInt(SettingsColumns.SNAP_HEADERS, sDefault.snapHeaders);
192        replyBehavior = json.optInt(SettingsColumns.REPLY_BEHAVIOR, sDefault.replyBehavior);
193        convListIcon = json.optInt(SettingsColumns.CONV_LIST_ICON, sDefault.convListIcon);
194        convListAttachmentPreviews = json.optBoolean(SettingsColumns.CONV_LIST_ATTACHMENT_PREVIEWS,
195                sDefault.convListAttachmentPreviews);
196        confirmDelete = json.optBoolean(SettingsColumns.CONFIRM_DELETE, sDefault.confirmDelete);
197        confirmArchive = json.optBoolean(SettingsColumns.CONFIRM_ARCHIVE, sDefault.confirmArchive);
198        confirmSend = json.optBoolean(SettingsColumns.CONFIRM_SEND, sDefault.confirmSend);
199        defaultInbox = Utils.getValidUri( json.optString(SettingsColumns.DEFAULT_INBOX));
200        defaultInboxName = json.optString(SettingsColumns.DEFAULT_INBOX_NAME,
201                sDefault.defaultInboxName);
202        forceReplyFromDefault = json.optBoolean(SettingsColumns.FORCE_REPLY_FROM_DEFAULT,
203                sDefault.forceReplyFromDefault);
204        maxAttachmentSize =
205                json.optInt(SettingsColumns.MAX_ATTACHMENT_SIZE, sDefault.maxAttachmentSize);
206        swipe = json.optInt(SettingsColumns.SWIPE, sDefault.swipe);
207        priorityArrowsEnabled = json.optBoolean(SettingsColumns.PRIORITY_ARROWS_ENABLED,
208                sDefault.priorityArrowsEnabled);
209        setupIntentUri = Utils.getValidUri(json.optString(SettingsColumns.SETUP_INTENT_URI));
210        conversationViewMode = json.optInt(SettingsColumns.CONVERSATION_VIEW_MODE,
211                UIProvider.ConversationViewMode.UNDEFINED);
212        veiledAddressPattern = json.optString(SettingsColumns.VEILED_ADDRESS_PATTERN, null);
213        moveToInbox = Utils.getValidUri(json.optString(SettingsColumns.MOVE_TO_INBOX));
214    }
215
216    /**
217     * Return a serialized String for these settings.
218     */
219    public synchronized String serialize() {
220        final JSONObject json = toJSON();
221        return json.toString();
222    }
223
224    private static Object getNonNull(Object candidate, Object fallback){
225        if (candidate == null)
226            return fallback;
227        return candidate;
228    }
229
230    /**
231     * Return a JSONObject for these settings.
232     */
233    public synchronized JSONObject toJSON() {
234        final JSONObject json = new JSONObject();
235        try {
236            json.put(SettingsColumns.SIGNATURE, getNonNull(signature, sDefault.signature));
237            json.put(SettingsColumns.AUTO_ADVANCE, getAutoAdvanceSetting());
238            json.put(SettingsColumns.MESSAGE_TEXT_SIZE, messageTextSize);
239            json.put(SettingsColumns.SNAP_HEADERS, snapHeaders);
240            json.put(SettingsColumns.REPLY_BEHAVIOR, replyBehavior);
241            json.put(SettingsColumns.CONV_LIST_ICON, convListIcon);
242            json.put(SettingsColumns.CONV_LIST_ATTACHMENT_PREVIEWS, convListAttachmentPreviews);
243            json.put(SettingsColumns.CONFIRM_DELETE, confirmDelete);
244            json.put(SettingsColumns.CONFIRM_ARCHIVE, confirmArchive);
245            json.put(SettingsColumns.CONFIRM_SEND, confirmSend);
246            json.put(SettingsColumns.DEFAULT_INBOX,
247                    getNonNull(defaultInbox, sDefault.defaultInbox));
248            json.put(SettingsColumns.DEFAULT_INBOX_NAME,
249                    getNonNull(defaultInboxName, sDefault.defaultInboxName));
250            json.put(SettingsColumns.FORCE_REPLY_FROM_DEFAULT, forceReplyFromDefault);
251            json.put(SettingsColumns.MAX_ATTACHMENT_SIZE,  maxAttachmentSize);
252            json.put(SettingsColumns.SWIPE, swipe);
253            json.put(SettingsColumns.PRIORITY_ARROWS_ENABLED, priorityArrowsEnabled);
254            json.put(SettingsColumns.SETUP_INTENT_URI, setupIntentUri);
255            json.put(SettingsColumns.CONVERSATION_VIEW_MODE, conversationViewMode);
256            json.put(SettingsColumns.VEILED_ADDRESS_PATTERN, veiledAddressPattern);
257            json.put(SettingsColumns.MOVE_TO_INBOX,
258                    getNonNull(moveToInbox, sDefault.moveToInbox));
259        } catch (JSONException e) {
260            LogUtils.wtf(LOG_TAG, e, "Could not serialize settings");
261        }
262        return json;
263    }
264
265    /**
266     * Creates a {@link Map} where the column name is the key and the value is the value.
267     * @param map map to insert values into or null
268     * @return the resulting map
269     */
270    public Map<String, Object> getValueMap(Map<String, Object> map) {
271        if (map == null) {
272            map = new HashMap<String, Object>();
273        }
274
275        map.put(UIProvider.AccountColumns.SettingsColumns.SIGNATURE, signature);
276        map.put(UIProvider.AccountColumns.SettingsColumns.AUTO_ADVANCE, getAutoAdvanceSetting());
277        map.put(UIProvider.AccountColumns.SettingsColumns.MESSAGE_TEXT_SIZE, messageTextSize);
278        map.put(UIProvider.AccountColumns.SettingsColumns.SNAP_HEADERS, snapHeaders);
279        map.put(UIProvider.AccountColumns.SettingsColumns.REPLY_BEHAVIOR, replyBehavior);
280        map.put(UIProvider.AccountColumns.SettingsColumns.CONV_LIST_ICON, convListIcon);
281        map.put(UIProvider.AccountColumns.SettingsColumns.CONV_LIST_ATTACHMENT_PREVIEWS,
282                convListAttachmentPreviews ? 1 : 0);
283        map.put(UIProvider.AccountColumns.SettingsColumns.CONFIRM_DELETE, confirmDelete ? 1 : 0);
284        map.put(UIProvider.AccountColumns.SettingsColumns.CONFIRM_ARCHIVE, confirmArchive ? 1 : 0);
285        map.put(UIProvider.AccountColumns.SettingsColumns.CONFIRM_SEND, confirmSend ? 1 : 0);
286        map.put(UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX, defaultInbox);
287        map.put(UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX_NAME, defaultInboxName);
288        map.put(UIProvider.AccountColumns.SettingsColumns.FORCE_REPLY_FROM_DEFAULT,
289                forceReplyFromDefault ? 1 : 0);
290        map.put(UIProvider.AccountColumns.SettingsColumns.MAX_ATTACHMENT_SIZE, maxAttachmentSize);
291        map.put(UIProvider.AccountColumns.SettingsColumns.SWIPE, swipe);
292        map.put(UIProvider.AccountColumns.SettingsColumns.PRIORITY_ARROWS_ENABLED,
293                priorityArrowsEnabled ? 1 : 0);
294        map.put(UIProvider.AccountColumns.SettingsColumns.SETUP_INTENT_URI, setupIntentUri);
295        map.put(UIProvider.AccountColumns.SettingsColumns.CONVERSATION_VIEW_MODE,
296                conversationViewMode);
297        map.put(UIProvider.AccountColumns.SettingsColumns.VEILED_ADDRESS_PATTERN,
298                veiledAddressPattern);
299        map.put(UIProvider.AccountColumns.SettingsColumns.MOVE_TO_INBOX, moveToInbox);
300
301        return map;
302    }
303
304    /**
305     * Create a new instance of an Settings object using a JSONObject  instance created previously
306     * using {@link #toJSON()}. This returns null if the serialized instance was invalid or does
307     * not represent a valid account object.
308     *
309     * @param json Serialized object
310     * @return New settings object or null
311     */
312    public static Settings newInstance(JSONObject json) {
313        if (json == null) {
314            return null;
315        }
316        return new Settings(json);
317    }
318
319    @Override
320    public int describeContents() {
321        return 0;
322    }
323
324    @Override
325    public void writeToParcel(Parcel dest, int flags) {
326        dest.writeString((String) getNonNull(signature, sDefault.signature));
327        dest.writeInt(getAutoAdvanceSetting());
328        dest.writeInt(messageTextSize);
329        dest.writeInt(snapHeaders);
330        dest.writeInt(replyBehavior);
331        dest.writeInt(convListIcon);
332        dest.writeInt(convListAttachmentPreviews ? 1 : 0);
333        dest.writeInt(confirmDelete ? 1 : 0);
334        dest.writeInt(confirmArchive? 1 : 0);
335        dest.writeInt(confirmSend? 1 : 0);
336        dest.writeString(getNonNull(defaultInbox, sDefault.defaultInbox).toString());
337        dest.writeString((String) getNonNull(defaultInboxName, sDefault.defaultInboxName));
338        dest.writeInt(forceReplyFromDefault ? 1 : 0);
339        dest.writeInt(maxAttachmentSize);
340        dest.writeInt(swipe);
341        dest.writeInt(priorityArrowsEnabled ? 1 : 0);
342        dest.writeString(getNonNull(setupIntentUri, sDefault.setupIntentUri).toString());
343        dest.writeInt(conversationViewMode);
344        dest.writeString(veiledAddressPattern);
345        dest.writeString(getNonNull(moveToInbox, sDefault.moveToInbox).toString());
346    }
347
348    /**
349     * Returns the URI of the current account's default inbox if available, otherwise
350     * returns the empty URI {@link Uri#EMPTY}
351     * @param settings a settings object, possibly null.
352     * @return a valid default Inbox URI, or {@link Uri#EMPTY} if settings are null or no default
353     * is specified.
354     */
355    public static Uri getDefaultInboxUri(Settings settings) {
356        if (settings == null) {
357            return sDefault.defaultInbox;
358        }
359        return (Uri) getNonNull(settings.defaultInbox, sDefault.defaultInbox);
360    }
361
362    /**
363     * Gets the autoadvance setting for this object, which may have changed since the settings were
364     * initially loaded.
365     */
366    public int getAutoAdvanceSetting() {
367        if (mTransientAutoAdvance != null) {
368            return mTransientAutoAdvance;
369        }
370
371        return mAutoAdvance;
372    }
373
374    /**
375     * Sets the transient autoadvance setting, which will override the initial autoadvance setting.
376     */
377    public void setAutoAdvanceSetting(final int autoAdvance) {
378        mTransientAutoAdvance = autoAdvance;
379    }
380
381    /**
382     * @return true if {@link UIProvider.ConversationViewMode#OVERVIEW} mode is set. In the event
383     * that the setting is not yet set, fall back to
384     * {@link UIProvider.ConversationViewMode#DEFAULT}.
385     */
386    public boolean isOverviewMode() {
387        final int val = (conversationViewMode != UIProvider.ConversationViewMode.UNDEFINED) ?
388                conversationViewMode : UIProvider.ConversationViewMode.DEFAULT;
389        return (val == UIProvider.ConversationViewMode.OVERVIEW);
390    }
391
392    /**
393     * Return the swipe setting for the settings provided. It is safe to pass this method
394     * a null object. It always returns a valid {@link Swipe} setting.
395     * @return the auto advance setting, a constant from {@link Swipe}
396     */
397    public static int getSwipeSetting(Settings settings) {
398        return settings != null ? settings.swipe : sDefault.swipe;
399    }
400
401    @SuppressWarnings("hiding")
402    public static final Creator<Settings> CREATOR = new Creator<Settings>() {
403        @Override
404        public Settings createFromParcel(Parcel source) {
405            return new Settings(source);
406        }
407
408        @Override
409        public Settings[] newArray(int size) {
410            return new Settings[size];
411        }
412    };
413
414    /**
415     *  Get the maximum size in bytes for attachments.
416     */
417    public int getMaxAttachmentSize() {
418        return maxAttachmentSize <= 0 ? DEFAULT_MAX_ATTACHMENT_SIZE : maxAttachmentSize;
419    }
420
421    @Override
422    public boolean equals(final Object aThat) {
423        LogUtils.d(LOG_TAG, "Settings.equals(%s)", aThat);
424        if (this == aThat) {
425            return true;
426        }
427        if ((aThat == null) || (aThat.getClass() != this.getClass())) {
428            return false;
429        }
430        final Settings that = (Settings) aThat;
431        final boolean autoAdvanceEquals = mTransientAutoAdvance != null
432                ? mTransientAutoAdvance.equals(that.mTransientAutoAdvance)
433                : that.mTransientAutoAdvance == null;
434        return (TextUtils.equals(signature, that.signature)
435                && mAutoAdvance == that.mAutoAdvance
436                && autoAdvanceEquals
437                && messageTextSize == that.messageTextSize
438                && snapHeaders == that.snapHeaders
439                && replyBehavior == that.replyBehavior
440                && convListIcon == that.convListIcon
441                && convListAttachmentPreviews == that.convListAttachmentPreviews
442                && confirmDelete == that.confirmDelete
443                && confirmArchive == that.confirmArchive
444                && confirmSend == that.confirmSend
445                && Objects.equal(defaultInbox, that.defaultInbox)
446                // Not checking default Inbox name, since is is identical to the URI check above.
447                && forceReplyFromDefault == that.forceReplyFromDefault
448                && maxAttachmentSize == that.maxAttachmentSize
449                && swipe == that.swipe
450                && priorityArrowsEnabled == that.priorityArrowsEnabled
451                && setupIntentUri == that.setupIntentUri
452                && conversationViewMode == that.conversationViewMode
453                && TextUtils.equals(veiledAddressPattern, that.veiledAddressPattern))
454                && Objects.equal(moveToInbox, that.moveToInbox);
455    }
456
457    @Override
458    public int hashCode() {
459        if (mHashCode == 0) {
460            mHashCode = super.hashCode()
461                    ^ Objects.hashCode(signature, mAutoAdvance, mTransientAutoAdvance,
462                    messageTextSize, snapHeaders, replyBehavior, convListIcon,
463                    convListAttachmentPreviews, confirmDelete, confirmArchive, confirmSend,
464                    defaultInbox, forceReplyFromDefault, maxAttachmentSize, swipe,
465                    priorityArrowsEnabled, setupIntentUri, conversationViewMode,
466                    veiledAddressPattern, moveToInbox);
467        }
468        return mHashCode;
469    }
470}
471