AbstractConversationViewFragment.java revision 606dbd7a44b8445e872c25c0fe080e3e12a47adf
1f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp/*
2f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Copyright (C) 2012 Google Inc.
3f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Licensed to The Android Open Source Project.
4f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp *
5f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Licensed under the Apache License, Version 2.0 (the "License");
6f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * you may not use this file except in compliance with the License.
7f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * You may obtain a copy of the License at
8f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp *
9f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp *      http://www.apache.org/licenses/LICENSE-2.0
10f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp *
11f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Unless required by applicable law or agreed to in writing, software
12f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * distributed under the License is distributed on an "AS IS" BASIS,
13f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * See the License for the specific language governing permissions and
15f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * limitations under the License.
16f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp */
17f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
18f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyppackage com.android.mail.ui;
19f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
20f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.app.Activity;
21f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.app.Fragment;
22f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.app.LoaderManager;
23f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.content.Context;
24f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.content.Loader;
25f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.database.Cursor;
26f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.net.Uri;
27f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.os.Bundle;
28ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindypimport android.os.Handler;
29f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.view.Menu;
30f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.view.MenuInflater;
31f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.view.MenuItem;
32f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
33f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.R;
348f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huangimport com.android.mail.browse.ConversationAccountController;
358812d3c50e35c4f2a02d29c35c76082c4ebec0cdAndrew Sappersteinimport com.android.mail.browse.ConversationMessage;
36f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.browse.ConversationViewHeader.ConversationViewHeaderCallbacks;
379d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huangimport com.android.mail.browse.MessageCursor;
38f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.browse.MessageCursor.ConversationController;
39c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrookimport com.android.mail.content.ObjectCursor;
40c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrookimport com.android.mail.content.ObjectCursorLoader;
41f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.Account;
42f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.AccountObserver;
43f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.Address;
44f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.Conversation;
45f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.ListParams;
46f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.UIProvider;
47a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwalimport com.android.mail.providers.UIProvider.CursorStatus;
48f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.LogTag;
49f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.LogUtils;
50f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.Utils;
51f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
524d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrookimport java.util.Arrays;
53543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huangimport java.util.Collections;
54543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huangimport java.util.HashMap;
55f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport java.util.Map;
56376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein
57f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
58f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyppublic abstract class AbstractConversationViewFragment extends Fragment implements
594ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein        ConversationController, ConversationAccountController,
60f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        ConversationViewHeaderCallbacks {
61f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
62606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    protected static final String ARG_ACCOUNT = "account";
63f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public static final String ARG_CONVERSATION = "conversation";
64f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private static final String LOG_TAG = LogTag.getLogTag();
65f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected static final int MESSAGE_LOADER = 0;
66f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected static final int CONTACT_LOADER = 1;
67f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected ControllableActivity mActivity;
68f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private final MessageLoaderCallbacks mMessageLoaderCallbacks = new MessageLoaderCallbacks();
69376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein    private ContactLoaderCallbacks mContactLoaderCallbacks;
70f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private MenuItem mChangeFoldersMenuItem;
71f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected Conversation mConversation;
72f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected String mBaseUri;
73f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected Account mAccount;
74376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein
75376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein    /**
76376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein     * Must be instantiated in a derived class's onCreate.
77376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein     */
78376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein    protected AbstractConversationWebViewClient mWebViewClient;
794ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein
80543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang    /**
81543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang     * Cache of email address strings to parsed Address objects.
82543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang     * <p>
83543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang     * Remember to synchronize on the map when reading or writing to this cache, because some
84543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang     * instances use it off the UI thread (e.g. from WebView).
85543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang     */
86543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang    protected final Map<String, Address> mAddressCache = Collections.synchronizedMap(
87543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang            new HashMap<String, Address>());
88f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private MessageCursor mCursor;
89f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private Context mContext;
909d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    /**
919d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * A backwards-compatible version of {{@link #getUserVisibleHint()}. Like the framework flag,
929d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * this flag is saved and restored.
939d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     */
949d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private boolean mUserVisible;
95376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein
96ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    private final Handler mHandler = new Handler();
97d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal    /** True if we want to avoid marking the conversation as viewed and read. */
98d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal    private boolean mSuppressMarkingViewed;
994d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    /**
1004d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook     * Parcelable state of the conversation view. Can safely be used without null checking any time
1019d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * after {@link #onCreate(Bundle)}.
1024d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook     */
1034d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    protected ConversationViewState mViewState;
1044d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
10518176786f320b4d4312ad1682da057434f201c13Scott Kennedy    private boolean mIsDetached;
10618176786f320b4d4312ad1682da057434f201c13Scott Kennedy
1072fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    private boolean mHasConversationBeenTransformed;
1082fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    private boolean mHasConversationTransformBeenReverted;
1092fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein
110f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private final AccountObserver mAccountObserver = new AccountObserver() {
111f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
112f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void onChanged(Account newAccount) {
113adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            final Account oldAccount = mAccount;
114f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mAccount = newAccount;
115376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein            mWebViewClient.setAccount(mAccount);
116adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            onAccountChanged(newAccount, oldAccount);
117f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
118f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    };
119f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
1209d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private static final String BUNDLE_VIEW_STATE =
1219d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            AbstractConversationViewFragment.class.getName() + "viewstate";
1229a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang    /**
1239a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang     * We save the user visible flag so the various transitions that occur during rotation do not
1249a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang     * cause unnecessary visibility change.
1259a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang     */
1269d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private static final String BUNDLE_USER_VISIBLE =
1279d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            AbstractConversationViewFragment.class.getName() + "uservisible";
1289d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
12918176786f320b4d4312ad1682da057434f201c13Scott Kennedy    private static final String BUNDLE_DETACHED =
13018176786f320b4d4312ad1682da057434f201c13Scott Kennedy            AbstractConversationViewFragment.class.getName() + "detached";
13118176786f320b4d4312ad1682da057434f201c13Scott Kennedy
1322fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    private static final String BUNDLE_KEY_HAS_CONVERSATION_BEEN_TRANSFORMED =
1332fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein            AbstractConversationViewFragment.class.getName() + "conversationtransformed";
1342fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    private static final String BUNDLE_KEY_HAS_CONVERSATION_BEEN_REVERTED =
1352fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein            AbstractConversationViewFragment.class.getName() + "conversationreverted";
1362fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein
137606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    public static Bundle makeBasicArgs(Account account) {
138f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        Bundle args = new Bundle();
139f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        args.putParcelable(ARG_ACCOUNT, account);
140f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return args;
141f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
142f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
143f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
144f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Constructor needs to be public to handle orientation changes and activity
145f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * lifecycle events.
146f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
147f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public AbstractConversationViewFragment() {
148f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super();
149f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
150f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
151f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
152f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override, since this depends on how many messages are
153f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * shown in the conversation view.
154f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
155d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal    protected void markUnread() {
156d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        // Do not automatically mark this conversation viewed and read.
157d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        mSuppressMarkingViewed = true;
158d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal    }
159f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
160f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
161f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override this, since they may want to display a single or
162f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * many messages related to this conversation.
163f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
164c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook    protected abstract void onMessageCursorLoadFinished(
165c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook            Loader<ObjectCursor<ConversationMessage>> loader,
166014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            MessageCursor newCursor, MessageCursor oldCursor);
167f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
168f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
169f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override this, since they may want to display a single or
170f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * many messages related to this conversation.
171f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
172f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
173f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public abstract void onConversationViewHeaderHeightChange(int newHeight);
174f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
175f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public abstract void onUserVisibleHintChanged();
176f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
177f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
178f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override this.
179f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
180adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    protected abstract void onAccountChanged(Account newAccount, Account oldAccount);
181f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
182f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
183f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onCreate(Bundle savedState) {
184f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onCreate(savedState);
185f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
186606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        parseArguments();
187606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        setBaseUri();
188ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook
189f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        LogUtils.d(LOG_TAG, "onCreate in ConversationViewFragment (this=%s)", this);
190f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // Not really, we just want to get a crack to store a reference to the change_folder item
191f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        setHasOptionsMenu(true);
1929d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
1939d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (savedState != null) {
1949d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mViewState = savedState.getParcelable(BUNDLE_VIEW_STATE);
1959d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mUserVisible = savedState.getBoolean(BUNDLE_USER_VISIBLE);
19618176786f320b4d4312ad1682da057434f201c13Scott Kennedy            mIsDetached = savedState.getBoolean(BUNDLE_DETACHED, false);
1972fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein            mHasConversationBeenTransformed =
1982fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein                    savedState.getBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_TRANSFORMED, false);
1992fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein            mHasConversationTransformBeenReverted =
2002fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein                    savedState.getBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_REVERTED, false);
2019d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        } else {
2029d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mViewState = getNewViewState();
2032fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein            mHasConversationBeenTransformed = false;
2042fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein            mHasConversationTransformBeenReverted = false;
2059d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
206f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
207f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
208606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    /**
209606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein     * Can be overridden in case a subclass needs to get additional arguments.
210606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein     */
211606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    protected void parseArguments() {
212606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        final Bundle args = getArguments();
213606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        mAccount = args.getParcelable(ARG_ACCOUNT);
214606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        mConversation = args.getParcelable(ARG_CONVERSATION);
215606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    }
216606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein
217606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    /**
218606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein     * Can be overridden in case a subclass needs a different uri format
219606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein     * (such as one that does not rely on account and/or conversation.
220606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein     */
221606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    protected void setBaseUri() {
222606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        // Since the uri specified in the conversation base uri may not be unique, we specify a
223606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        // base uri that us guaranteed to be unique for this conversation.
224606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        mBaseUri = "x-thread://" + mAccount.name.hashCode() + "/" + mConversation.id;
225606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    }
226606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein
2279e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang    @Override
2289e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang    public String toString() {
2299e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        // log extra info at DEBUG level or finer
2309e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        final String s = super.toString();
2319e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        if (!LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG) || mConversation == null) {
2329e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang            return s;
2339e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        }
2349e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        return "(" + s + " conv=" + mConversation + ")";
2359e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang    }
2369e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang
237f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
238f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onActivityCreated(Bundle savedInstanceState) {
239f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onActivityCreated(savedInstanceState);
2408fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal        final Activity activity = getActivity();
241f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (!(activity instanceof ControllableActivity)) {
242f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            LogUtils.wtf(LOG_TAG, "ConversationViewFragment expects only a ControllableActivity to"
243f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    + "create it. Cannot proceed.");
244f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
2458fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal        if (activity == null || activity.isFinishing()) {
246f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // Activity is finishing, just bail.
247f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return;
248f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
2498fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal        mActivity = (ControllableActivity) activity;
250f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mContext = activity.getApplicationContext();
251b622d2b6750423c185a2a2463277e5e2b853fadaAndy Huang        mWebViewClient.setActivity(activity);
252f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mAccount = mAccountObserver.initialize(mActivity.getAccountController());
253376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein        mWebViewClient.setAccount(mAccount);
254f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
255f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
256f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
257f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public ConversationUpdater getListController() {
258f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        final ControllableActivity activity = (ControllableActivity) getActivity();
259f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return activity != null ? activity.getConversationUpdater() : null;
260f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
261f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
262f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public Context getContext() {
263f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mContext;
264f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
265f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
26602133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang    @Override
267f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public Conversation getConversation() {
268f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mConversation;
269f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
270f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
271f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
272f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public MessageCursor getMessageCursor() {
273f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mCursor;
274f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
275f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
276ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    public Handler getHandler() {
277ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        return mHandler;
278ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    }
279ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
280f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public MessageLoaderCallbacks getMessageLoaderCallbacks() {
281f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mMessageLoaderCallbacks;
282f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
283f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
284f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public ContactLoaderCallbacks getContactInfoSource() {
285376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein        if (mContactLoaderCallbacks == null) {
286376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein            mContactLoaderCallbacks = new ContactLoaderCallbacks(mActivity.getActivityContext());
287376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein        }
288f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mContactLoaderCallbacks;
289f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
290f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
291f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
292f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public Account getAccount() {
293f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mAccount;
294f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
295f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
296f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
297f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
298f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onCreateOptionsMenu(menu, inflater);
299f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mChangeFoldersMenuItem = menu.findItem(R.id.change_folder);
300f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
301f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
302f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
303f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public boolean onOptionsItemSelected(MenuItem item) {
304bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang        if (!isUserVisible()) {
305bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            // Unclear how this is happening. Current theory is that this fragment was scheduled
306bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            // to be removed, but the remove transaction failed. When the Activity is later
307bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            // restored, the FragmentManager restores this fragment, but Fragment.mMenuVisible is
308bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            // stuck at its initial value (true), which makes this zombie fragment eligible for
309bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            // menu item clicks.
310bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            //
311bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            // Work around this by relying on the (properly restored) extra user visible hint.
312bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            LogUtils.e(LOG_TAG,
313bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang                    "ACVF ignoring onOptionsItemSelected b/c userVisibleHint is false. f=%s", this);
314bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
315b184bfe96fa3512af88260fce4f3cee3066fb28dScott Kennedy                LogUtils.e(LOG_TAG, Utils.dumpFragment(this));  // the dump has '%' chars in it...
316bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            }
317bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            return false;
318bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang        }
319bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang
320f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        boolean handled = false;
321f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        switch (item.getItemId()) {
322f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            case R.id.inside_conversation_unread:
323f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                markUnread();
324f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                handled = true;
325f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                break;
3262fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein            case R.id.show_original:
3272fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein                showUntransformedConversation();
3282fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein                handled = true;
3292fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein                break;
330f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
331f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return handled;
332f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
333f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
3342fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    @Override
3352fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    public void onPrepareOptionsMenu(Menu menu) {
3362fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein        // Only show option if we support message transforms and message has been transformed.
3372fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein        Utils.setMenuItemVisibility(menu, R.id.show_original, supportsMessageTransforms() &&
3382fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein                mHasConversationBeenTransformed && !mHasConversationTransformBeenReverted);
3392fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    }
3402fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein
3414ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein    abstract boolean supportsMessageTransforms();
3424ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein
343f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    // BEGIN conversation header callbacks
344f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
345f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onFoldersClicked() {
346f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (mChangeFoldersMenuItem == null) {
347f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            LogUtils.e(LOG_TAG, "unable to open 'change folders' dialog for a conversation");
348f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return;
349f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
350f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mActivity.onOptionsItemSelected(mChangeFoldersMenuItem);
351f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
352f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    // END conversation header callbacks
353f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
354f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
3559d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    public void onSaveInstanceState(Bundle outState) {
3569d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (mViewState != null) {
3579d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            outState.putParcelable(BUNDLE_VIEW_STATE, mViewState);
3589d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
3599d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        outState.putBoolean(BUNDLE_USER_VISIBLE, mUserVisible);
36018176786f320b4d4312ad1682da057434f201c13Scott Kennedy        outState.putBoolean(BUNDLE_DETACHED, mIsDetached);
3612fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein        outState.putBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_TRANSFORMED,
3622fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein                mHasConversationBeenTransformed);
3632fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein        outState.putBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_REVERTED,
3642fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein                mHasConversationTransformBeenReverted);
3659d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
3669d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
3679d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    @Override
368f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onDestroyView() {
369f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onDestroyView();
370f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mAccountObserver.unregisterAndDestroy();
371f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
372f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
373f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
374f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * {@link #setUserVisibleHint(boolean)} only works on API >= 15, so implement our own for
375f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * reliability on older platforms.
376f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
377f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void setExtraUserVisibleHint(boolean isVisibleToUser) {
378f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        LogUtils.v(LOG_TAG, "in CVF.setHint, val=%s (%s)", isVisibleToUser, this);
379f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (mUserVisible != isVisibleToUser) {
380f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mUserVisible = isVisibleToUser;
381bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            MessageCursor cursor = getMessageCursor();
3820b9b48c1ac510d6d0d043a86354880ceb57a8175mindyp            if (mUserVisible && (cursor != null && cursor.isLoaded() && cursor.getCount() == 0)) {
383bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                // Pop back to conversation list and show error.
384bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                onError();
385bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                return;
386bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            }
387f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            onUserVisibleHintChanged();
388f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
389f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
390f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
3919d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    public boolean isUserVisible() {
3929d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        return mUserVisible;
3939d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
3949d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
395243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang    protected void timerMark(String msg) {
396243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang        if (isUserVisible()) {
397243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang            Utils.sConvLoadTimer.mark(msg);
398243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang        }
399243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang    }
400243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang
401c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook    private class MessageLoaderCallbacks
402c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook            implements LoaderManager.LoaderCallbacks<ObjectCursor<ConversationMessage>> {
403f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
404f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
405c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook        public Loader<ObjectCursor<ConversationMessage>> onCreateLoader(int id, Bundle args) {
40602133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang            return new MessageLoader(mActivity.getActivityContext(), mConversation.messageListUri);
407f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
408f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
409f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
410c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook        public void onLoadFinished(Loader<ObjectCursor<ConversationMessage>> loader,
411c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook                    ObjectCursor<ConversationMessage> data) {
412f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // ignore truly duplicate results
413f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // this can happen when restoring after rotation
414f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (mCursor == data) {
415f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                return;
416f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            } else {
4176766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang                final MessageCursor messageCursor = (MessageCursor) data;
418f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
41902133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang                // bind the cursor to this fragment so it can access to the current list controller
42002133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang                messageCursor.setController(AbstractConversationViewFragment.this);
42102133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang
422f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
423f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    LogUtils.d(LOG_TAG, "LOADED CONVERSATION= %s", messageCursor.getDebugDump());
424f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
425f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
426a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal                // We have no messages: exit conversation view.
427a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal                if (messageCursor.getCount() == 0
42818176786f320b4d4312ad1682da057434f201c13Scott Kennedy                        && (!CursorStatus.isWaitingForResults(messageCursor.getStatus())
42918176786f320b4d4312ad1682da057434f201c13Scott Kennedy                                || mIsDetached)) {
430f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    if (mUserVisible) {
431bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                        onError();
432f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    } else {
433f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // we expect that the pager adapter will remove this
434f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // conversation fragment on its own due to a separate
435f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // conversation cursor update (we might get here if the
436f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // message list update fires first. nothing to do
437f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // because we expect to be torn down soon.)
438f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        LogUtils.i(LOG_TAG, "CVF: offscreen conv has no messages, ignoring update"
439c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook                                + " in anticipation of conv cursor update. c=%s",
440c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook                                mConversation.uri);
441f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    }
442233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // existing mCursor will imminently be closed, must stop referencing it
443233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // since we expect to be kicked out soon, it doesn't matter what mCursor
444233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // becomes
445233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    mCursor = null;
446f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    return;
447f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
448f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
449f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // ignore cursors that are still loading results
450f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                if (!messageCursor.isLoaded()) {
451233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // existing mCursor will imminently be closed, must stop referencing it
452233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // in this case, the new cursor is also no good, and since don't expect to get
453233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // here except in initial load situations, it's safest to just ensure the
454233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // reference is null
455233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    mCursor = null;
456f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    return;
457f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
458014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                final MessageCursor oldCursor = mCursor;
4596766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang                mCursor = messageCursor;
460014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                onMessageCursorLoadFinished(loader, mCursor, oldCursor);
461f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            }
462f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
463f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
464f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
465c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook        public void onLoaderReset(Loader<ObjectCursor<ConversationMessage>>  loader) {
466f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mCursor = null;
467f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
468f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
469f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
470f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
471bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp    private void onError() {
472bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // need to exit this view- conversation may have been
473bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // deleted, or for whatever reason is now invalid (e.g.
474bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // discard single draft)
475bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        //
476bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // N.B. this may involve a fragment transaction, which
477bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // FragmentManager will refuse to execute directly
478bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // within onLoadFinished. Make sure the controller knows.
479bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        LogUtils.i(LOG_TAG, "CVF: visible conv has no messages, exiting conv mode");
480bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // TODO(mindyp): handle ERROR status by showing an error
481bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // message to the user that there are no messages in
482bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // this conversation
48318176786f320b4d4312ad1682da057434f201c13Scott Kennedy        popOut();
48418176786f320b4d4312ad1682da057434f201c13Scott Kennedy    }
485bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp
48618176786f320b4d4312ad1682da057434f201c13Scott Kennedy    private void popOut() {
487376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein        mHandler.post(new FragmentRunnable("popOut", this) {
488bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            @Override
4899a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            public void go() {
4907729a9aaa1873505191303a35c480b6dcf1c7382Alice Yang                if (mActivity != null) {
4917729a9aaa1873505191303a35c480b6dcf1c7382Alice Yang                    mActivity.getListHandler()
4927729a9aaa1873505191303a35c480b6dcf1c7382Alice Yang                            .onConversationSelected(null, true /* inLoaderCallbacks */);
4937729a9aaa1873505191303a35c480b6dcf1c7382Alice Yang                }
494bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            }
495bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        });
496bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp    }
497bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp
4984d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    protected void onConversationSeen() {
499919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy        LogUtils.d(LOG_TAG, "AbstractConversationViewFragment#onConversationSeen()");
500919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy
5014d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // Ignore unsafe calls made after a fragment is detached from an activity
5024d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        final ControllableActivity activity = (ControllableActivity) getActivity();
5034d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        if (activity == null) {
5044d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            LogUtils.w(LOG_TAG, "ignoring onConversationSeen for conv=%s", mConversation.id);
5054d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            return;
5064d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
5074d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
5084d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        mViewState.setInfoForConversation(mConversation);
5094d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
510919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy        LogUtils.d(LOG_TAG, "onConversationSeen() - mSuppressMarkingViewed = %b",
511919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy                mSuppressMarkingViewed);
512d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        // In most circumstances we want to mark the conversation as viewed and read, since the
513d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        // user has read it.  However, if the user has already marked the conversation unread, we
514d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        // do not want a  later mark-read operation to undo this.  So we check this variable which
515d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        // is set in #markUnread() which suppresses automatic mark-read.
516d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        if (!mSuppressMarkingViewed) {
517d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal            // mark viewed/read if not previously marked viewed by this conversation view,
518d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal            // or if unread messages still exist in the message list cursor
519d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal            // we don't want to keep marking viewed on rotation or restore
520d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal            // but we do want future re-renders to mark read (e.g. "New message from X" case)
521214522998157088ec7e38e0d2b4497e43f310ab7Paul Westbrook            final MessageCursor cursor = getMessageCursor();
522919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy            LogUtils.d(LOG_TAG, "onConversationSeen() - mConversation.isViewed() = %b, "
523919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy                    + "cursor null = %b, cursor.isConversationRead() = %b",
524919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy                    mConversation.isViewed(), cursor == null,
525919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy                    cursor != null && cursor.isConversationRead());
526d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal            if (!mConversation.isViewed() || (cursor != null && !cursor.isConversationRead())) {
527d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal                // Mark the conversation viewed and read.
528d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal                activity.getConversationUpdater()
529d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal                        .markConversationsRead(Arrays.asList(mConversation), true, true);
530d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal
531d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal                // and update the Message objects in the cursor so the next time a cursor update
532d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal                // happens with these messages marked read, we know to ignore it
533214522998157088ec7e38e0d2b4497e43f310ab7Paul Westbrook                if (cursor != null && !cursor.isClosed()) {
534d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal                    cursor.markMessagesRead();
535d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal                }
5364d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            }
5374d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
5383b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        activity.getListHandler().onConversationSeen();
5394d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    }
5404d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
5414d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    protected ConversationViewState getNewViewState() {
5424d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        return new ConversationViewState();
5434d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    }
5444d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
545c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook    private static class MessageLoader extends ObjectCursorLoader<ConversationMessage> {
546f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private boolean mDeliveredFirstResults = false;
547f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
54802133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang        public MessageLoader(Context c, Uri messageListUri) {
549c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook            super(c, messageListUri, UIProvider.MESSAGE_PROJECTION, ConversationMessage.FACTORY);
550f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
551f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
552f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
553c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook        public void deliverResult(ObjectCursor<ConversationMessage> result) {
554f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // We want to deliver these results, and then we want to make sure
555f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // that any subsequent
556f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // queries do not hit the network
557f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            super.deliverResult(result);
558f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
559f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (!mDeliveredFirstResults) {
560f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                mDeliveredFirstResults = true;
561f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                Uri uri = getUri();
562f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
563f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // Create a ListParams that tells the provider to not hit the
564f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // network
565f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                final ListParams listParams = new ListParams(ListParams.NO_LIMIT,
566f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        false /* useNetwork */);
567f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
568f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // Build the new uri with this additional parameter
569f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                uri = uri
570f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        .buildUpon()
571f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        .appendQueryParameter(UIProvider.LIST_PARAMS_QUERY_PARAMETER,
572f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                                listParams.serialize()).build();
573f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                setUri(uri);
574f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            }
575f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
576c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook
577c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook        @Override
578c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook        protected ObjectCursor<ConversationMessage> getObjectCursor(Cursor inner) {
579c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook            return new MessageCursor(inner);
580c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook        }
581f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
582f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
58326d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp    public abstract void onConversationUpdated(Conversation conversation);
58426d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp
58518176786f320b4d4312ad1682da057434f201c13Scott Kennedy    public void onDetachedModeEntered() {
58618176786f320b4d4312ad1682da057434f201c13Scott Kennedy        // If we have no messages, then we have nothing to display, so leave this view.
58718176786f320b4d4312ad1682da057434f201c13Scott Kennedy        // Otherwise, just set the detached flag.
58818176786f320b4d4312ad1682da057434f201c13Scott Kennedy        final Cursor messageCursor = getMessageCursor();
58918176786f320b4d4312ad1682da057434f201c13Scott Kennedy
59018176786f320b4d4312ad1682da057434f201c13Scott Kennedy        if (messageCursor == null || messageCursor.getCount() == 0) {
59118176786f320b4d4312ad1682da057434f201c13Scott Kennedy            popOut();
59218176786f320b4d4312ad1682da057434f201c13Scott Kennedy        } else {
59318176786f320b4d4312ad1682da057434f201c13Scott Kennedy            mIsDetached = true;
59418176786f320b4d4312ad1682da057434f201c13Scott Kennedy        }
59518176786f320b4d4312ad1682da057434f201c13Scott Kennedy    }
5962fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein
5972fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    /**
5982fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * Called when the JavaScript reports that it transformed a message.
5992fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * Sets a flag to true and invalidates the options menu so it will
6002fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * include the "Revert auto-sizing" menu option.
6012fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     */
6022fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    public void onConversationTransformed() {
6032fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein        mHasConversationBeenTransformed = true;
604376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein        mHandler.post(new FragmentRunnable("invalidateOptionsMenu", this) {
605ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein            @Override
606ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein            public void go() {
607ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein                mActivity.invalidateOptionsMenu();
608ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein            }
609ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein        });
6102fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    }
6112fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein
6122fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    /**
6132fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * Called when the "Revert auto-sizing" option is selected. Default
6142fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * implementation simply sets a value on whether transforms should be
6152fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * applied. Derived classes should override this class and force a
6162fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * re-render so that the conversation renders without
6172fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     */
6182fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    public void showUntransformedConversation() {
6192fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein        // must set the value to true so we don't show the options menu item again
6202fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein        mHasConversationTransformBeenReverted = true;
6212fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    }
6222fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein
6232fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    /**
6242fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * Returns {@code true} if the conversation should be transformed. {@code false}, otherwise.
6252fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * @return {@code true} if the conversation should be transformed. {@code false}, otherwise.
6262fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     */
6272fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    public boolean shouldApplyTransforms() {
6283617b41394337774fb2e6fcf9fef1ac9eca00482Alice Yang        return (mAccount.enableMessageTransforms > 0) &&
6293617b41394337774fb2e6fcf9fef1ac9eca00482Alice Yang                !mHasConversationTransformBeenReverted;
6302fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    }
631f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp}
632