1d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd/* 2d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Copyright (C) 2015 The Android Open Source Project 3d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 4d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Licensed under the Apache License, Version 2.0 (the "License"); 5d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * you may not use this file except in compliance with the License. 6d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * You may obtain a copy of the License at 7d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 8d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * http://www.apache.org/licenses/LICENSE-2.0 9d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 10d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Unless required by applicable law or agreed to in writing, software 11d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * distributed under the License is distributed on an "AS IS" BASIS, 12d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * See the License for the specific language governing permissions and 14d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * limitations under the License. 15d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 16d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddpackage com.android.messaging.ui.conversation; 17d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 18d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.os.Parcel; 19d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.os.Parcelable; 20d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 21d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.ui.contact.ContactPickerFragment; 22d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.Assert; 23d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.google.common.annotations.VisibleForTesting; 24d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 25d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd/** 26d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Keeps track of the different UI states that the ConversationActivity may be in. This acts as 27d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * a state machine which, based on different actions (e.g. onAddMoreParticipants), notifies the 28d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * ConversationActivity about any state UI change so it can update the visuals. This class 29d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * implements Parcelable and it's persisted across activity tear down and relaunch. 30d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 31d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddpublic class ConversationActivityUiState implements Parcelable, Cloneable { 32d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd interface ConversationActivityUiStateHost { 33d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd void onConversationContactPickerUiStateChanged(int oldState, int newState, boolean animate); 34d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 35d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 36d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /*------ Overall UI states (conversation & contact picker) ------*/ 37d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 38d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** Only a full screen conversation is showing. */ 39d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public static final int STATE_CONVERSATION_ONLY = 1; 40d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** Only a full screen contact picker is showing asking user to pick the initial contact. */ 41d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public static final int STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT = 2; 42d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 43d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Only a full screen contact picker is showing asking user to pick more participants. This 44d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * happens after the user picked the initial contact, and then decide to go back and add more. 45d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 46d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public static final int STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS = 3; 47d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 48d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Only a full screen contact picker is showing asking user to pick more participants. However 49d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * user has reached max number of conversation participants and can add no more. 50d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 51d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public static final int STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS = 4; 52d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 53d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * A hybrid mode where the conversation view + contact chips view are showing. This happens 54d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * right after the user picked the initial contact for which a 1-1 conversation is fetched or 55d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * created. 56d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 57d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public static final int STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW = 5; 58d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 59d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // The overall UI state of the ConversationActivity. 60d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private int mConversationContactUiState; 61d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 62d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // The currently displayed conversation (if any). 63d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private String mConversationId; 64d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 65d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Indicates whether we should put focus in the compose message view when the 66d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // ConversationFragment is attached. This is a transient state that's not persisted as 67d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // part of the parcelable. 68d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private boolean mPendingResumeComposeMessage = false; 69d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 70d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // The owner ConversationActivity. This is not parceled since the instance always change upon 71d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // object reuse. 72d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private ConversationActivityUiStateHost mHost; 73d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 74d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Indicates the owning ConverastionActivity is in the process of updating its UI presentation 75d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // to be in sync with the UI states. Outside of the UI updates, the UI states here should 76d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // ALWAYS be consistent with the actual states of the activity. 77d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private int mUiUpdateCount; 78d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 79d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 80d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Create a new instance with an initial conversation id. 81d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 82d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd ConversationActivityUiState(final String conversationId) { 83d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // The conversation activity may be initialized with only one of two states: 84d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Conversation-only (when there's a conversation id) or picking initial contact 85d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // (when no conversation id is given). 86d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mConversationId = conversationId; 87d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mConversationContactUiState = conversationId == null ? 88d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT : STATE_CONVERSATION_ONLY; 89d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 90d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 91d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void setHost(final ConversationActivityUiStateHost host) { 92d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mHost = host; 93d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 94d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 95d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public boolean shouldShowConversationFragment() { 96d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW || 97d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mConversationContactUiState == STATE_CONVERSATION_ONLY; 98d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 99d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 100d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public boolean shouldShowContactPickerFragment() { 101d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS || 102d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS || 103d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT || 104d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW; 105d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 106d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 107d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 108d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Returns whether there's a pending request to resume message compose (i.e. set focus to 109d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * the compose message view and show the soft keyboard). If so, this request will be served 110d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * when the conversation fragment get created and resumed. This happens when the user commits 111d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * participant selection for a group conversation and goes back to the conversation fragment. 112d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Since conversation fragment creation happens asynchronously, we issue and track this 113d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * pending request for it to be eventually fulfilled. 114d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 115d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public boolean shouldResumeComposeMessage() { 116d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (mPendingResumeComposeMessage) { 117d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // This is a one-shot operation that just keeps track of the pending resume compose 118d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // state. This is also a non-critical operation so we don't care about failure case. 119d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPendingResumeComposeMessage = false; 120d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return true; 121d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 122d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return false; 123d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 124d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 125d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public int getDesiredContactPickingMode() { 126d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd switch (mConversationContactUiState) { 127d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd case STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS: 128d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return ContactPickerFragment.MODE_PICK_MORE_CONTACTS; 129d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd case STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS: 130d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return ContactPickerFragment.MODE_PICK_MAX_PARTICIPANTS; 131d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd case STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT: 132d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return ContactPickerFragment.MODE_PICK_INITIAL_CONTACT; 133d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd case STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW: 134d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return ContactPickerFragment.MODE_CHIPS_ONLY; 135d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd default: 136d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.fail("Invalid contact picking mode for ConversationActivity!"); 137d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return ContactPickerFragment.MODE_UNDEFINED; 138d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 139d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 140d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 141d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public String getConversationId() { 142d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return mConversationId; 143d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 144d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 145d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 146d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Called whenever the contact picker fragment successfully fetched or created a conversation. 147d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 148d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void onGetOrCreateConversation(final String conversationId) { 149d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int newState = STATE_CONVERSATION_ONLY; 150d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT) { 151d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd newState = STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW; 152d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } else if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS || 153d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS) { 154d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd newState = STATE_CONVERSATION_ONLY; 155d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } else { 156d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // New conversation should only be created when we are in one of the contact picking 157d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // modes. 158d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.fail("Invalid conversation activity state: can't create conversation!"); 159d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 160d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mConversationId = conversationId; 161d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd performUiStateUpdate(newState, true); 162d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 163d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 164d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 165d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Called when the user started composing message. If we are in the hybrid chips state, we 166d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * should commit to enter the conversation only state. 167d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 168d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void onStartMessageCompose() { 169d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // This cannot happen when we are in one of the full-screen contact picking states. 170d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.isTrue(mConversationContactUiState != STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT && 171d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mConversationContactUiState != STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS && 172d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mConversationContactUiState != STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS); 173d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW) { 174d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd performUiStateUpdate(STATE_CONVERSATION_ONLY, true); 175d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 176d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 177d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 178d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 179d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Called when the user initiated an action to add more participants in the hybrid state, 180d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * namely clicking on the "add more participants" button or entered a new contact chip via 181d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * auto-complete. 182d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 183d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void onAddMoreParticipants() { 184d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW) { 185d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPendingResumeComposeMessage = true; 186d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd performUiStateUpdate(STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS, true); 187d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } else { 188d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // This is only possible in the hybrid state. 189d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.fail("Invalid conversation activity state: can't add more participants!"); 190d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 191d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 192d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 193d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 194d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Called each time the number of participants is updated to check against the limit and 195d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * update the ui state accordingly. 196d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 197d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void onParticipantCountUpdated(final boolean canAddMoreParticipants) { 198d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS 199d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd && !canAddMoreParticipants) { 200d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd performUiStateUpdate(STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS, false); 201d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } else if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS 202d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd && canAddMoreParticipants) { 203d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd performUiStateUpdate(STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS, false); 204d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 205d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 206d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 207d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void performUiStateUpdate(final int conversationContactState, final boolean animate) { 208d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // This starts one UI update cycle, during which we allow the conversation activity's 209d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // UI presentation to be temporarily out of sync with the states here. 210d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd beginUiUpdate(); 211d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 212d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (conversationContactState != mConversationContactUiState) { 213d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int oldState = mConversationContactUiState; 214d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mConversationContactUiState = conversationContactState; 215d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd notifyOnOverallUiStateChanged(oldState, mConversationContactUiState, animate); 216d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 217d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd endUiUpdate(); 218d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 219d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 220d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void notifyOnOverallUiStateChanged( 221d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int oldState, final int newState, final boolean animate) { 222d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Always verify state validity whenever we have a state change. 223d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd assertValidState(); 224d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.isTrue(isUiUpdateInProgress()); 225d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 226d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Only do this if we are still attached to the host. mHost can be null if the host 227d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // activity is already destroyed, but due to timing the contained UI components may still 228d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // receive events such as focus change and trigger a callback to the Ui state. We'd like 229d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // to guard against those cases. 230d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (mHost != null) { 231d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mHost.onConversationContactPickerUiStateChanged(oldState, newState, animate); 232d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 233d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 234d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 235d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void assertValidState() { 236d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Conversation id may be null IF AND ONLY IF the user is picking the initial contact to 237d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // start a conversation. 238d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.isTrue((mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT) == 239d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd (mConversationId == null)); 240d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 241d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 242d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void beginUiUpdate() { 243d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mUiUpdateCount++; 244d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 245d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 246d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void endUiUpdate() { 247d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (--mUiUpdateCount < 0) { 248d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.fail("Unbalanced Ui updates!"); 249d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 250d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 251d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 252d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private boolean isUiUpdateInProgress() { 253d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return mUiUpdateCount > 0; 254d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 255d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 256d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 257d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public int describeContents() { 258d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return 0; 259d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 260d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 261d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 262d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void writeToParcel(final Parcel dest, final int flags) { 263d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd dest.writeInt(mConversationContactUiState); 264d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd dest.writeString(mConversationId); 265d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 266d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 267d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private ConversationActivityUiState(final Parcel in) { 268d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mConversationContactUiState = in.readInt(); 269d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mConversationId = in.readString(); 270d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 271d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Always verify state validity whenever we initialize states. 272d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd assertValidState(); 273d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 274d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 275d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public static final Parcelable.Creator<ConversationActivityUiState> CREATOR 276d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd = new Parcelable.Creator<ConversationActivityUiState>() { 277d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 278d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public ConversationActivityUiState createFromParcel(final Parcel in) { 279d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return new ConversationActivityUiState(in); 280d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 281d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 282d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 283d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public ConversationActivityUiState[] newArray(final int size) { 284d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return new ConversationActivityUiState[size]; 285d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 286d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd }; 287d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 288d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 289d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd protected ConversationActivityUiState clone() { 290d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd try { 291d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return (ConversationActivityUiState) super.clone(); 292d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } catch (CloneNotSupportedException e) { 293d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.fail("ConversationActivityUiState: failed to clone(). Is there a mutable " + 294d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd "reference?"); 295d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 296d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return null; 297d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 298d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 299d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 300d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * allows for overridding the internal UI state. Should never be called except by test code. 301d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 302d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @VisibleForTesting 303d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd void testSetUiState(final int uiState) { 304d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mConversationContactUiState = uiState; 305d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 306d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd} 307