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    };
96
97    public ViewMode() {
98        // Do nothing
99    }
100
101    @Override
102    public String toString() {
103        return "[mode=" + MODE_NAMES[mMode] + "]";
104    }
105
106    public String getModeString() {
107        return MODE_NAMES[mMode];
108    }
109
110    /**
111     * Adds a listener from this view mode.
112     * Must happen in the UI thread.
113     */
114    public void addListener(ModeChangeListener listener) {
115        mListeners.add(listener);
116    }
117
118    /**
119     * Dispatches a change event for the mode.
120     * Always happens in the UI thread.
121     */
122    private void dispatchModeChange() {
123        ArrayList<ModeChangeListener> list = new ArrayList<ModeChangeListener>(mListeners);
124        for (ModeChangeListener listener : list) {
125            assert (listener != null);
126            listener.onViewModeChanged(mMode);
127        }
128    }
129
130    /**
131     * Requests a transition of the mode to show the conversation list as the prominent view.
132     *
133     */
134    public void enterConversationListMode() {
135        setModeInternal(CONVERSATION_LIST);
136    }
137
138    /**
139     * Requests a transition of the mode to show a conversation as the prominent view.
140     *
141     */
142    public void enterConversationMode() {
143        setModeInternal(CONVERSATION);
144    }
145
146    /**
147     * Requests a transition of the mode to show a list of search results as the
148     * prominent view.
149     *
150     */
151    public void enterSearchResultsListMode() {
152        setModeInternal(SEARCH_RESULTS_LIST);
153    }
154
155    /**
156     * Requests a transition of the mode to show a conversation that was part of
157     * search results.
158     *
159     */
160    public void enterSearchResultsConversationMode() {
161        setModeInternal(SEARCH_RESULTS_CONVERSATION);
162    }
163
164    /**
165     * Requests a transition of the mode to show the "waiting for sync" messages
166     *
167     */
168    public void enterWaitingForInitializationMode() {
169        setModeInternal(WAITING_FOR_ACCOUNT_INITIALIZATION);
170    }
171
172    /**
173     * Requests a transition of the mode to show an ad.
174     */
175    public void enterAdMode() {
176        setModeInternal(AD);
177    }
178
179    /**
180     * @return The current mode.
181     */
182    public int getMode() {
183        return mMode;
184    }
185
186    /**
187     * Return whether the current mode is considered a list mode.
188     */
189    public boolean isListMode() {
190        return isListMode(mMode);
191    }
192
193    public static boolean isListMode(final int mode) {
194        return mode == CONVERSATION_LIST || mode == SEARCH_RESULTS_LIST;
195    }
196
197    public boolean isConversationMode() {
198        return isConversationMode(mMode);
199    }
200
201    public static boolean isConversationMode(final int mode) {
202        return mode == CONVERSATION || mode == SEARCH_RESULTS_CONVERSATION;
203    }
204
205    public static boolean isSearchMode(final int mode) {
206        return mode == SEARCH_RESULTS_LIST || mode == SEARCH_RESULTS_CONVERSATION;
207    }
208
209    public boolean isWaitingForSync() {
210        return isWaitingForSync(mMode);
211    }
212
213    public static boolean isWaitingForSync(final int mode) {
214        return mode == WAITING_FOR_ACCOUNT_INITIALIZATION;
215    }
216
217    public boolean isAdMode() {
218        return isAdMode(mMode);
219    }
220
221    public static boolean isAdMode(final int mode) {
222        return mode == AD;
223    }
224
225    /**
226     * Restoring from a saved state restores only the mode. It does not restore the listeners of
227     * this object.
228     * @param inState
229     */
230    public void handleRestore(Bundle inState) {
231        if (inState == null) {
232            return;
233        }
234        // Restore the previous mode, and UNKNOWN if nothing exists.
235        final int newMode = inState.getInt(VIEW_MODE_KEY, UNKNOWN);
236        if (newMode != UNKNOWN) {
237            setModeInternal(newMode);
238        }
239    }
240
241    /**
242     * Save the existing mode only. Does not save the existing listeners.
243     * @param outState
244     */
245    public void handleSaveInstanceState(Bundle outState) {
246        if (outState == null) {
247            return;
248        }
249        outState.putInt(VIEW_MODE_KEY, mMode);
250    }
251
252    /**
253     * Removes a listener from this view mode.
254     * Must happen in the UI thread.
255     */
256    public void removeListener(ModeChangeListener listener) {
257        mListeners.remove(listener);
258    }
259
260    /**
261     * Sets the internal mode.
262     * @return Whether or not a change occurred.
263     */
264    private boolean setModeInternal(int mode) {
265        if (mMode == mode) {
266            if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
267                LogUtils.d(LOG_TAG, new Error(), "ViewMode: debouncing change attempt mode=%s",
268                        mode);
269            } else {
270                LogUtils.i(LOG_TAG, "ViewMode: debouncing change attempt mode=%s", mode);
271            }
272            return false;
273        }
274
275        if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
276            LogUtils.d(LOG_TAG, new Error(), "ViewMode: executing change old=%s new=%s", mMode,
277                    mode);
278        } else {
279            LogUtils.i(LOG_TAG, "ViewMode: executing change old=%s new=%s", mMode, mode);
280        }
281
282        mMode = mode;
283        dispatchModeChange();
284        Analytics.getInstance().sendView("ViewMode" + toString());
285        return true;
286    }
287}
288