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
18package com.android.mail.ui;
19
20import android.os.Bundle;
21
22import com.android.mail.analytics.Analytics;
23import com.android.mail.utils.LogUtils;
24import com.google.common.collect.Lists;
25
26import java.util.ArrayList;
27
28/**
29 * Represents the view mode for the tablet Gmail activity.
30 * Transitions between modes should be done through this central object, and UI components that are
31 * dependent on the mode should listen to changes on this object.
32 */
33public class ViewMode {
34    /**
35     * A listener for changes on a ViewMode. To listen to mode changes, implement this
36     * interface and register your object with the single ViewMode held by the ActivityController
37     * instance. On mode changes, the onViewModeChanged method will be called with the new mode.
38     */
39    public interface ModeChangeListener {
40        /**
41         * Called when the mode has changed.
42         */
43        void onViewModeChanged(int newMode);
44    }
45
46    /**
47     * Mode when showing a single conversation.
48     */
49    public static final int CONVERSATION = 1;
50    /**
51     * Mode when showing a list of conversations
52     */
53    public static final int CONVERSATION_LIST = 2;
54    /**
55     * Mode when showing results from user search.
56     */
57    public static final int SEARCH_RESULTS_LIST = 3;
58    /**
59     * Mode when showing results from user search.
60     */
61    public static final int SEARCH_RESULTS_CONVERSATION = 4;
62    /**
63     * Mode when showing the "waiting for sync" message.
64     */
65    public static final int WAITING_FOR_ACCOUNT_INITIALIZATION = 5;
66    /**
67     * Mode when showing ads.
68     */
69    public static final int AD = 6;
70    /**
71     * Uncertain mode. The mode has not been initialized.
72     */
73    public static final int UNKNOWN = 0;
74
75    // Key used to save this {@link ViewMode}.
76    private static final String VIEW_MODE_KEY = "view-mode";
77    private final ArrayList<ModeChangeListener> mListeners = Lists.newArrayList();
78    /**
79     * The actual mode the activity is in. We start out with an UNKNOWN mode, and require entering
80     * a valid mode after the object has been created.
81     */
82    private int mMode = UNKNOWN;
83
84    public static final String LOG_TAG = "ViewMode";
85
86    // friendly names (not user-facing) for each view mode, indexed by ordinal value.
87    private static final String[] MODE_NAMES = {
88        "Unknown",
89        "Conversation",
90        "Conversation list",
91        "Search results list",
92        "Search results conversation",
93        "Waiting for sync",
94        "Ad",
95        "Warm welcome"
96    };
97
98    public ViewMode() {
99        // Do nothing
100    }
101
102    @Override
103    public String toString() {
104        return "[mode=" + MODE_NAMES[mMode] + "]";
105    }
106
107    public String getModeString() {
108        return MODE_NAMES[mMode];
109    }
110
111    /**
112     * Adds a listener from this view mode.
113     * Must happen in the UI thread.
114     */
115    public void addListener(ModeChangeListener listener) {
116        mListeners.add(listener);
117    }
118
119    /**
120     * Dispatches a change event for the mode.
121     * Always happens in the UI thread.
122     */
123    private void dispatchModeChange() {
124        ArrayList<ModeChangeListener> list = new ArrayList<ModeChangeListener>(mListeners);
125        for (ModeChangeListener listener : list) {
126            assert (listener != null);
127            listener.onViewModeChanged(mMode);
128        }
129    }
130
131    /**
132     * Requests a transition of the mode to show the conversation list as the prominent view.
133     *
134     */
135    public void enterConversationListMode() {
136        setModeInternal(CONVERSATION_LIST);
137    }
138
139    /**
140     * Requests a transition of the mode to show a conversation as the prominent view.
141     *
142     */
143    public void enterConversationMode() {
144        setModeInternal(CONVERSATION);
145    }
146
147    /**
148     * Requests a transition of the mode to show a list of search results as the
149     * prominent view.
150     *
151     */
152    public void enterSearchResultsListMode() {
153        setModeInternal(SEARCH_RESULTS_LIST);
154    }
155
156    /**
157     * Requests a transition of the mode to show a conversation that was part of
158     * search results.
159     *
160     */
161    public void enterSearchResultsConversationMode() {
162        setModeInternal(SEARCH_RESULTS_CONVERSATION);
163    }
164
165    /**
166     * Requests a transition of the mode to show the "waiting for sync" messages
167     *
168     */
169    public void enterWaitingForInitializationMode() {
170        setModeInternal(WAITING_FOR_ACCOUNT_INITIALIZATION);
171    }
172
173    /**
174     * Requests a transition of the mode to show an ad.
175     */
176    public void enterAdMode() {
177        setModeInternal(AD);
178    }
179
180    /**
181     * @return The current mode.
182     */
183    public int getMode() {
184        return mMode;
185    }
186
187    /**
188     * Return whether the current mode is considered a list mode.
189     */
190    public boolean isListMode() {
191        return isListMode(mMode);
192    }
193
194    public static boolean isListMode(final int mode) {
195        return mode == CONVERSATION_LIST || mode == SEARCH_RESULTS_LIST;
196    }
197
198    public boolean isConversationMode() {
199        return isConversationMode(mMode);
200    }
201
202    public static boolean isConversationMode(final int mode) {
203        return mode == CONVERSATION || mode == SEARCH_RESULTS_CONVERSATION;
204    }
205
206    public boolean isSearchMode() {
207        return isSearchMode(mMode);
208    }
209
210    public static boolean isSearchMode(final int mode) {
211        return mode == SEARCH_RESULTS_LIST || mode == SEARCH_RESULTS_CONVERSATION;
212    }
213
214    public boolean isWaitingForSync() {
215        return isWaitingForSync(mMode);
216    }
217
218    public static boolean isWaitingForSync(final int mode) {
219        return mode == WAITING_FOR_ACCOUNT_INITIALIZATION;
220    }
221
222    public boolean isAdMode() {
223        return isAdMode(mMode);
224    }
225
226    public static boolean isAdMode(final int mode) {
227        return mode == AD;
228    }
229
230    /**
231     * Restoring from a saved state restores only the mode. It does not restore the listeners of
232     * this object.
233     * @param inState
234     */
235    public void handleRestore(Bundle inState) {
236        if (inState == null) {
237            return;
238        }
239        // Restore the previous mode, and UNKNOWN if nothing exists.
240        final int newMode = inState.getInt(VIEW_MODE_KEY, UNKNOWN);
241        if (newMode != UNKNOWN) {
242            setModeInternal(newMode);
243        }
244    }
245
246    /**
247     * Save the existing mode only. Does not save the existing listeners.
248     * @param outState
249     */
250    public void handleSaveInstanceState(Bundle outState) {
251        if (outState == null) {
252            return;
253        }
254        outState.putInt(VIEW_MODE_KEY, mMode);
255    }
256
257    /**
258     * Removes a listener from this view mode.
259     * Must happen in the UI thread.
260     */
261    public void removeListener(ModeChangeListener listener) {
262        mListeners.remove(listener);
263    }
264
265    /**
266     * Sets the internal mode.
267     * @return Whether or not a change occurred.
268     */
269    private boolean setModeInternal(int mode) {
270        if (mMode == mode) {
271            if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
272                LogUtils.d(LOG_TAG, new Error(), "ViewMode: debouncing change attempt mode=%s",
273                        mode);
274            } else {
275                LogUtils.i(LOG_TAG, "ViewMode: debouncing change attempt mode=%s", mode);
276            }
277            return false;
278        }
279
280        if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
281            LogUtils.d(LOG_TAG, new Error(), "ViewMode: executing change old=%s new=%s", mMode,
282                    mode);
283        } else {
284            LogUtils.i(LOG_TAG, "ViewMode: executing change old=%s new=%s", mMode, mode);
285        }
286
287        mMode = mode;
288        dispatchModeChange();
289        Analytics.getInstance().sendView("ViewMode" + toString());
290        return true;
291    }
292}
293