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 18 package com.android.mail.ui; 19 20 import android.os.Bundle; 21 22 import com.android.mail.analytics.Analytics; 23 import com.android.mail.utils.LogUtils; 24 import com.google.common.collect.Lists; 25 26 import 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 */ 33 public 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