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