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;
293f6111cf87080fe7adba512c7b9dfad2e4cd8154James Lemieuximport android.support.annotation.Nullable;
30f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.view.Menu;
31f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.view.MenuInflater;
32f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.view.MenuItem;
33f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
34821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerimport com.android.emailcommon.mail.Address;
35f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.R;
36761522c1ac7a8fa05acde714f0e26fde47f3415fAndy Huangimport com.android.mail.analytics.Analytics;
378f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huangimport com.android.mail.browse.ConversationAccountController;
388812d3c50e35c4f2a02d29c35c76082c4ebec0cdAndrew Sappersteinimport com.android.mail.browse.ConversationMessage;
39f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.browse.ConversationViewHeader.ConversationViewHeaderCallbacks;
409d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huangimport com.android.mail.browse.MessageCursor;
41f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.browse.MessageCursor.ConversationController;
42c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrookimport com.android.mail.content.ObjectCursor;
43c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrookimport com.android.mail.content.ObjectCursorLoader;
44f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.Account;
45f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.AccountObserver;
46f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.Conversation;
47e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huangimport com.android.mail.providers.Folder;
48f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.ListParams;
498ec43e877a9c1925514f066655984e21fbd255e8Andrew Sappersteinimport com.android.mail.providers.Settings;
50f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.UIProvider;
51a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwalimport com.android.mail.providers.UIProvider.CursorStatus;
52f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.LogTag;
53f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.LogUtils;
54f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.Utils;
55f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
564d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrookimport java.util.Arrays;
57543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huangimport java.util.Collections;
58543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huangimport java.util.HashMap;
59f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport java.util.Map;
60376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein
61f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyppublic abstract class AbstractConversationViewFragment extends Fragment implements
624ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein        ConversationController, ConversationAccountController,
63f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        ConversationViewHeaderCallbacks {
64f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
65606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    protected static final String ARG_ACCOUNT = "account";
66f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public static final String ARG_CONVERSATION = "conversation";
67f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private static final String LOG_TAG = LogTag.getLogTag();
68f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected static final int MESSAGE_LOADER = 0;
69f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected static final int CONTACT_LOADER = 1;
704f347e811052f446c3958c76db278bcd7b39a44fAndy Huang    public static final int ATTACHMENT_OPTION1_LOADER = 2;
71f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected ControllableActivity mActivity;
72f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private final MessageLoaderCallbacks mMessageLoaderCallbacks = new MessageLoaderCallbacks();
73376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein    private ContactLoaderCallbacks mContactLoaderCallbacks;
74f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private MenuItem mChangeFoldersMenuItem;
75f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected Conversation mConversation;
76f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected String mBaseUri;
77f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected Account mAccount;
78376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein
79376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein    /**
80376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein     * Must be instantiated in a derived class's onCreate.
81376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein     */
82376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein    protected AbstractConversationWebViewClient mWebViewClient;
834ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein
84543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang    /**
85543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang     * Cache of email address strings to parsed Address objects.
86543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang     * <p>
87543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang     * Remember to synchronize on the map when reading or writing to this cache, because some
88543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang     * instances use it off the UI thread (e.g. from WebView).
89543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang     */
90543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang    protected final Map<String, Address> mAddressCache = Collections.synchronizedMap(
91543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang            new HashMap<String, Address>());
92f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private MessageCursor mCursor;
93f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private Context mContext;
949d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    /**
959d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * A backwards-compatible version of {{@link #getUserVisibleHint()}. Like the framework flag,
969d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * this flag is saved and restored.
979d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     */
989d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private boolean mUserVisible;
99376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein
100ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    private final Handler mHandler = new Handler();
101d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal    /** True if we want to avoid marking the conversation as viewed and read. */
102d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal    private boolean mSuppressMarkingViewed;
1034d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    /**
1044d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook     * Parcelable state of the conversation view. Can safely be used without null checking any time
1059d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * after {@link #onCreate(Bundle)}.
1064d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook     */
1074d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    protected ConversationViewState mViewState;
1084d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
10918176786f320b4d4312ad1682da057434f201c13Scott Kennedy    private boolean mIsDetached;
11018176786f320b4d4312ad1682da057434f201c13Scott Kennedy
1112fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    private boolean mHasConversationBeenTransformed;
1122fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    private boolean mHasConversationTransformBeenReverted;
1132fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein
114e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang    protected boolean mConversationSeen = false;
115e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang
116f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private final AccountObserver mAccountObserver = new AccountObserver() {
117f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
118f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void onChanged(Account newAccount) {
119adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            final Account oldAccount = mAccount;
120f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mAccount = newAccount;
121376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein            mWebViewClient.setAccount(mAccount);
122adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            onAccountChanged(newAccount, oldAccount);
123f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
124f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    };
125f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
1269d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private static final String BUNDLE_VIEW_STATE =
1279d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            AbstractConversationViewFragment.class.getName() + "viewstate";
1289a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang    /**
1299a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang     * We save the user visible flag so the various transitions that occur during rotation do not
1309a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang     * cause unnecessary visibility change.
1319a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang     */
1329d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private static final String BUNDLE_USER_VISIBLE =
1339d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            AbstractConversationViewFragment.class.getName() + "uservisible";
1349d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
13518176786f320b4d4312ad1682da057434f201c13Scott Kennedy    private static final String BUNDLE_DETACHED =
13618176786f320b4d4312ad1682da057434f201c13Scott Kennedy            AbstractConversationViewFragment.class.getName() + "detached";
13718176786f320b4d4312ad1682da057434f201c13Scott Kennedy
1382fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    private static final String BUNDLE_KEY_HAS_CONVERSATION_BEEN_TRANSFORMED =
1392fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein            AbstractConversationViewFragment.class.getName() + "conversationtransformed";
1402fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    private static final String BUNDLE_KEY_HAS_CONVERSATION_BEEN_REVERTED =
1412fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein            AbstractConversationViewFragment.class.getName() + "conversationreverted";
1422fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein
143606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    public static Bundle makeBasicArgs(Account account) {
144f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        Bundle args = new Bundle();
145f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        args.putParcelable(ARG_ACCOUNT, account);
146f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return args;
147f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
148f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
149f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
150f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Constructor needs to be public to handle orientation changes and activity
151f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * lifecycle events.
152f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
153f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public AbstractConversationViewFragment() {
154f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super();
155f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
156f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
157f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
158f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override, since this depends on how many messages are
159f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * shown in the conversation view.
160f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
161d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal    protected void markUnread() {
162d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        // Do not automatically mark this conversation viewed and read.
163d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        mSuppressMarkingViewed = true;
164d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal    }
165f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
166f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
16787a8982af83168453b6dc219fe984bff68047bebAndy Huang     * Marks a conversation either 'seen' (force=false), as in when the conversation is made visible
16887a8982af83168453b6dc219fe984bff68047bebAndy Huang     * and should be marked read, or 'read' (force=true), as in when the action bar menu item to
16987a8982af83168453b6dc219fe984bff68047bebAndy Huang     * mark this conversation read is selected.
17087a8982af83168453b6dc219fe984bff68047bebAndy Huang     *
17187a8982af83168453b6dc219fe984bff68047bebAndy Huang     * @param force true to force marking it read, false to allow peek mode to prevent it
17287a8982af83168453b6dc219fe984bff68047bebAndy Huang     */
17387a8982af83168453b6dc219fe984bff68047bebAndy Huang    private final void markRead(boolean force) {
17487a8982af83168453b6dc219fe984bff68047bebAndy Huang        final ControllableActivity activity = (ControllableActivity) getActivity();
17587a8982af83168453b6dc219fe984bff68047bebAndy Huang        if (activity == null) {
17687a8982af83168453b6dc219fe984bff68047bebAndy Huang            return;
17787a8982af83168453b6dc219fe984bff68047bebAndy Huang        }
17887a8982af83168453b6dc219fe984bff68047bebAndy Huang
17987a8982af83168453b6dc219fe984bff68047bebAndy Huang        // mark viewed/read if not previously marked viewed by this conversation view,
18087a8982af83168453b6dc219fe984bff68047bebAndy Huang        // or if unread messages still exist in the message list cursor
18187a8982af83168453b6dc219fe984bff68047bebAndy Huang        // we don't want to keep marking viewed on rotation or restore
18287a8982af83168453b6dc219fe984bff68047bebAndy Huang        // but we do want future re-renders to mark read (e.g. "New message from X" case)
18387a8982af83168453b6dc219fe984bff68047bebAndy Huang        final MessageCursor cursor = getMessageCursor();
18487a8982af83168453b6dc219fe984bff68047bebAndy Huang        LogUtils.d(LOG_TAG, "onConversationSeen() - mConversation.isViewed() = %b, "
18587a8982af83168453b6dc219fe984bff68047bebAndy Huang                + "cursor null = %b, cursor.isConversationRead() = %b",
18687a8982af83168453b6dc219fe984bff68047bebAndy Huang                mConversation.isViewed(), cursor == null,
18787a8982af83168453b6dc219fe984bff68047bebAndy Huang                cursor != null && cursor.isConversationRead());
18887a8982af83168453b6dc219fe984bff68047bebAndy Huang        if (!mConversation.isViewed() || (cursor != null && !cursor.isConversationRead())) {
18987a8982af83168453b6dc219fe984bff68047bebAndy Huang            // Mark the conversation read no matter what if force=true.
19087a8982af83168453b6dc219fe984bff68047bebAndy Huang            // else only mark it seen if appropriate (2-pane peek=true doesn't mark things seen)
19187a8982af83168453b6dc219fe984bff68047bebAndy Huang            final boolean convMarkedRead;
19287a8982af83168453b6dc219fe984bff68047bebAndy Huang            if (force) {
19387a8982af83168453b6dc219fe984bff68047bebAndy Huang                activity.getConversationUpdater()
19487a8982af83168453b6dc219fe984bff68047bebAndy Huang                        .markConversationsRead(Arrays.asList(mConversation), true /* read */,
19587a8982af83168453b6dc219fe984bff68047bebAndy Huang                                true /* viewed */);
19687a8982af83168453b6dc219fe984bff68047bebAndy Huang                convMarkedRead = true;
19787a8982af83168453b6dc219fe984bff68047bebAndy Huang            } else {
19887a8982af83168453b6dc219fe984bff68047bebAndy Huang                convMarkedRead = activity.getConversationUpdater()
19987a8982af83168453b6dc219fe984bff68047bebAndy Huang                        .markConversationSeen(mConversation);
20087a8982af83168453b6dc219fe984bff68047bebAndy Huang            }
20187a8982af83168453b6dc219fe984bff68047bebAndy Huang
20287a8982af83168453b6dc219fe984bff68047bebAndy Huang            // and update the Message objects in the cursor so the next time a cursor update
20387a8982af83168453b6dc219fe984bff68047bebAndy Huang            // happens with these messages marked read, we know to ignore it
20487a8982af83168453b6dc219fe984bff68047bebAndy Huang            if (convMarkedRead && cursor != null && !cursor.isClosed()) {
20587a8982af83168453b6dc219fe984bff68047bebAndy Huang                cursor.markMessagesRead();
20687a8982af83168453b6dc219fe984bff68047bebAndy Huang            }
20787a8982af83168453b6dc219fe984bff68047bebAndy Huang        }
20887a8982af83168453b6dc219fe984bff68047bebAndy Huang    }
20987a8982af83168453b6dc219fe984bff68047bebAndy Huang
21087a8982af83168453b6dc219fe984bff68047bebAndy Huang    /**
211f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override this, since they may want to display a single or
212f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * many messages related to this conversation.
213f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
214c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook    protected abstract void onMessageCursorLoadFinished(
215c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook            Loader<ObjectCursor<ConversationMessage>> loader,
216014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            MessageCursor newCursor, MessageCursor oldCursor);
217f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
218f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
219f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override this, since they may want to display a single or
220f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * many messages related to this conversation.
221f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
222f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
223f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public abstract void onConversationViewHeaderHeightChange(int newHeight);
224f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
225f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public abstract void onUserVisibleHintChanged();
226f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
227f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
228f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override this.
229f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
230adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    protected abstract void onAccountChanged(Account newAccount, Account oldAccount);
231f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
232f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
233f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onCreate(Bundle savedState) {
234f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onCreate(savedState);
235f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
236606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        parseArguments();
237606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        setBaseUri();
238ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook
239f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        LogUtils.d(LOG_TAG, "onCreate in ConversationViewFragment (this=%s)", this);
240f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // Not really, we just want to get a crack to store a reference to the change_folder item
241f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        setHasOptionsMenu(true);
2429d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
2439d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (savedState != null) {
2449d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mViewState = savedState.getParcelable(BUNDLE_VIEW_STATE);
2459d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mUserVisible = savedState.getBoolean(BUNDLE_USER_VISIBLE);
24618176786f320b4d4312ad1682da057434f201c13Scott Kennedy            mIsDetached = savedState.getBoolean(BUNDLE_DETACHED, false);
2472fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein            mHasConversationBeenTransformed =
2482fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein                    savedState.getBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_TRANSFORMED, false);
2492fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein            mHasConversationTransformBeenReverted =
2502fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein                    savedState.getBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_REVERTED, false);
2519d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        } else {
2529d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mViewState = getNewViewState();
2532fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein            mHasConversationBeenTransformed = false;
2542fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein            mHasConversationTransformBeenReverted = false;
2559d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
256f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
257f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
258606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    /**
259606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein     * Can be overridden in case a subclass needs to get additional arguments.
260606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein     */
261606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    protected void parseArguments() {
262606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        final Bundle args = getArguments();
263606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        mAccount = args.getParcelable(ARG_ACCOUNT);
264606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        mConversation = args.getParcelable(ARG_CONVERSATION);
265606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    }
266606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein
267606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    /**
268606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein     * Can be overridden in case a subclass needs a different uri format
269606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein     * (such as one that does not rely on account and/or conversation.
270606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein     */
271606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    protected void setBaseUri() {
272ee04e666deb8bb194f742f762462fc043733d666Ray Chen        mBaseUri = buildBaseUri(getContext(), mAccount, mConversation);
273562c5ba7235948cf1d20a9afa40e67cd62f43cf7Andrew Sapperstein    }
274562c5ba7235948cf1d20a9afa40e67cd62f43cf7Andrew Sapperstein
2754b0c0127d24e387a4f49d442b63b5c60cedb6922Ray Chen    public static String buildBaseUri(Context context, Account account, Conversation conversation) {
276606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        // Since the uri specified in the conversation base uri may not be unique, we specify a
277606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        // base uri that us guaranteed to be unique for this conversation.
278fd79237327f6803675cad2a64a74411ebda854e3Paul Westbrook        return "x-thread://" + account.getAccountId().hashCode() + "/" + conversation.id;
279606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    }
280606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein
2819e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang    @Override
2829e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang    public String toString() {
2839e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        // log extra info at DEBUG level or finer
2849e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        final String s = super.toString();
2859e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        if (!LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG) || mConversation == null) {
2869e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang            return s;
2879e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        }
2889e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        return "(" + s + " conv=" + mConversation + ")";
2899e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang    }
2909e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang
291f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
292f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onActivityCreated(Bundle savedInstanceState) {
293f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onActivityCreated(savedInstanceState);
2948fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal        final Activity activity = getActivity();
295f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (!(activity instanceof ControllableActivity)) {
296f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            LogUtils.wtf(LOG_TAG, "ConversationViewFragment expects only a ControllableActivity to"
297f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    + "create it. Cannot proceed.");
298f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
2998fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal        if (activity == null || activity.isFinishing()) {
300f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // Activity is finishing, just bail.
301f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return;
302f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
3038fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal        mActivity = (ControllableActivity) activity;
304f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mContext = activity.getApplicationContext();
305b622d2b6750423c185a2a2463277e5e2b853fadaAndy Huang        mWebViewClient.setActivity(activity);
306f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mAccount = mAccountObserver.initialize(mActivity.getAccountController());
307376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein        mWebViewClient.setAccount(mAccount);
308f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
309f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
310f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
311f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public ConversationUpdater getListController() {
312f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        final ControllableActivity activity = (ControllableActivity) getActivity();
313f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return activity != null ? activity.getConversationUpdater() : null;
314f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
315f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
316f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public Context getContext() {
317f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mContext;
318f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
319f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
32002133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang    @Override
321f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public Conversation getConversation() {
322f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mConversation;
323f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
324f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
325f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
3263f6111cf87080fe7adba512c7b9dfad2e4cd8154James Lemieux    public @Nullable MessageCursor getMessageCursor() {
327f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mCursor;
328f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
329f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
330ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    public Handler getHandler() {
331ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        return mHandler;
332ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    }
333ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
334f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public MessageLoaderCallbacks getMessageLoaderCallbacks() {
335f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mMessageLoaderCallbacks;
336f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
337f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
338f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public ContactLoaderCallbacks getContactInfoSource() {
339376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein        if (mContactLoaderCallbacks == null) {
3408913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein            mContactLoaderCallbacks = mActivity.getContactLoaderCallbacks();
341376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein        }
342f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mContactLoaderCallbacks;
343f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
344f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
345f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
346f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public Account getAccount() {
347f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mAccount;
348f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
349f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
350f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
351f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
352f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onCreateOptionsMenu(menu, inflater);
3536c570db71b8adc43d7dc2f31e6fa63cf50c1426eAndrew Sapperstein        mChangeFoldersMenuItem = menu.findItem(R.id.change_folders);
354f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
355f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
356f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
357f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public boolean onOptionsItemSelected(MenuItem item) {
358bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang        if (!isUserVisible()) {
359bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            // Unclear how this is happening. Current theory is that this fragment was scheduled
360bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            // to be removed, but the remove transaction failed. When the Activity is later
361bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            // restored, the FragmentManager restores this fragment, but Fragment.mMenuVisible is
362bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            // stuck at its initial value (true), which makes this zombie fragment eligible for
363bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            // menu item clicks.
364bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            //
365bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            // Work around this by relying on the (properly restored) extra user visible hint.
366bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            LogUtils.e(LOG_TAG,
367bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang                    "ACVF ignoring onOptionsItemSelected b/c userVisibleHint is false. f=%s", this);
368bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
36954a9057dade988a0fd2ff585cf477492d86669e8Tony Mantler                LogUtils.e(LOG_TAG, "%s", Utils.dumpFragment(this));
370bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            }
371bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang            return false;
372bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang        }
373bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang
374a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao        boolean handled = true;
3752b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy        final int itemId = item.getItemId();
376a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao        if (itemId == R.id.inside_conversation_unread || itemId == R.id.toggle_read_unread) {
3772b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy            markUnread();
37887a8982af83168453b6dc219fe984bff68047bebAndy Huang        } else if (itemId == R.id.read) {
37987a8982af83168453b6dc219fe984bff68047bebAndy Huang            markRead(true /* force */);
38087a8982af83168453b6dc219fe984bff68047bebAndy Huang            mActivity.supportInvalidateOptionsMenu();
3812b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy        } else if (itemId == R.id.show_original) {
3822b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy            showUntransformedConversation();
3836293ef09d934e191148279a72036f927366f82f0Andrew Sapperstein        } else if (itemId == R.id.print_all) {
3845c1692a5faeab220881a17a3427a8986ef874403Andrew Sapperstein            printConversation();
385a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao        } else if (itemId == R.id.reply) {
386a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao            handleReply();
387a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao        } else if (itemId == R.id.reply_all) {
388a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao            handleReplyAll();
389a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao        } else {
390a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao            handled = false;
391f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
392f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return handled;
393f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
394f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
3952fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    @Override
3962fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    public void onPrepareOptionsMenu(Menu menu) {
3972fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein        // Only show option if we support message transforms and message has been transformed.
3981e5bccefa627306120fbfd783838e1ef8b7fe0d8Jin Cao        Utils.setMenuItemPresent(menu, R.id.show_original, supportsMessageTransforms() &&
3992fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein                mHasConversationBeenTransformed && !mHasConversationTransformBeenReverted);
4007cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux
4017cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux        final MenuItem printMenuItem = menu.findItem(R.id.print_all);
4027cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux        if (printMenuItem != null) {
4037cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux            // compute the visibility of the print menu item
4047cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux            printMenuItem.setVisible(Utils.isRunningKitkatOrLater() && shouldShowPrintInOverflow());
4057cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux
4067cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux            // compute the text displayed on the print menu item
4077cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux            if (mConversation.getNumMessages() == 1) {
4087cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux                printMenuItem.setTitle(R.string.print);
4097cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux            } else {
4107cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux                printMenuItem.setTitle(R.string.print_all);
4117cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux            }
4127cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux        }
4132fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    }
4142fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein
4154ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein    abstract boolean supportsMessageTransforms();
4164ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein
417f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    // BEGIN conversation header callbacks
418f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
419f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onFoldersClicked() {
420f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (mChangeFoldersMenuItem == null) {
421f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            LogUtils.e(LOG_TAG, "unable to open 'change folders' dialog for a conversation");
422f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return;
423f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
424f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mActivity.onOptionsItemSelected(mChangeFoldersMenuItem);
425f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
426f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    // END conversation header callbacks
427f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
428f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
429761522c1ac7a8fa05acde714f0e26fde47f3415fAndy Huang    public void onStart() {
430761522c1ac7a8fa05acde714f0e26fde47f3415fAndy Huang        super.onStart();
431761522c1ac7a8fa05acde714f0e26fde47f3415fAndy Huang
432761522c1ac7a8fa05acde714f0e26fde47f3415fAndy Huang        Analytics.getInstance().sendView(getClass().getName());
433761522c1ac7a8fa05acde714f0e26fde47f3415fAndy Huang    }
434761522c1ac7a8fa05acde714f0e26fde47f3415fAndy Huang
435761522c1ac7a8fa05acde714f0e26fde47f3415fAndy Huang    @Override
4369d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    public void onSaveInstanceState(Bundle outState) {
4379d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (mViewState != null) {
4389d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            outState.putParcelable(BUNDLE_VIEW_STATE, mViewState);
4399d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
4409d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        outState.putBoolean(BUNDLE_USER_VISIBLE, mUserVisible);
44118176786f320b4d4312ad1682da057434f201c13Scott Kennedy        outState.putBoolean(BUNDLE_DETACHED, mIsDetached);
4422fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein        outState.putBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_TRANSFORMED,
4432fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein                mHasConversationBeenTransformed);
4442fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein        outState.putBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_REVERTED,
4452fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein                mHasConversationTransformBeenReverted);
4469d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
4479d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
4489d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    @Override
449f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onDestroyView() {
450f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onDestroyView();
451f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mAccountObserver.unregisterAndDestroy();
452f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
453f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
454f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
455f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * {@link #setUserVisibleHint(boolean)} only works on API >= 15, so implement our own for
456f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * reliability on older platforms.
457f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
458f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void setExtraUserVisibleHint(boolean isVisibleToUser) {
459f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        LogUtils.v(LOG_TAG, "in CVF.setHint, val=%s (%s)", isVisibleToUser, this);
460f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (mUserVisible != isVisibleToUser) {
461f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mUserVisible = isVisibleToUser;
462bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            MessageCursor cursor = getMessageCursor();
4630b9b48c1ac510d6d0d043a86354880ceb57a8175mindyp            if (mUserVisible && (cursor != null && cursor.isLoaded() && cursor.getCount() == 0)) {
464bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                // Pop back to conversation list and show error.
465bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                onError();
466bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                return;
467bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            }
468f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            onUserVisibleHintChanged();
469f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
470f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
471f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
4729d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    public boolean isUserVisible() {
4739d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        return mUserVisible;
4749d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
4759d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
476243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang    protected void timerMark(String msg) {
477243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang        if (isUserVisible()) {
478243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang            Utils.sConvLoadTimer.mark(msg);
479243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang        }
480243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang    }
481243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang
482c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook    private class MessageLoaderCallbacks
483c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook            implements LoaderManager.LoaderCallbacks<ObjectCursor<ConversationMessage>> {
484f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
485f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
486c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook        public Loader<ObjectCursor<ConversationMessage>> onCreateLoader(int id, Bundle args) {
48702133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang            return new MessageLoader(mActivity.getActivityContext(), mConversation.messageListUri);
488f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
489f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
490f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
491c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook        public void onLoadFinished(Loader<ObjectCursor<ConversationMessage>> loader,
492c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook                    ObjectCursor<ConversationMessage> data) {
493f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // ignore truly duplicate results
494f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // this can happen when restoring after rotation
495f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (mCursor == data) {
496f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                return;
497f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            } else {
4986766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang                final MessageCursor messageCursor = (MessageCursor) data;
499f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
50002133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang                // bind the cursor to this fragment so it can access to the current list controller
50102133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang                messageCursor.setController(AbstractConversationViewFragment.this);
50202133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang
503f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
504f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    LogUtils.d(LOG_TAG, "LOADED CONVERSATION= %s", messageCursor.getDebugDump());
505f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
506f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
507a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal                // We have no messages: exit conversation view.
508a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal                if (messageCursor.getCount() == 0
50918176786f320b4d4312ad1682da057434f201c13Scott Kennedy                        && (!CursorStatus.isWaitingForResults(messageCursor.getStatus())
51018176786f320b4d4312ad1682da057434f201c13Scott Kennedy                                || mIsDetached)) {
511f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    if (mUserVisible) {
512bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                        onError();
513f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    } else {
514f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // we expect that the pager adapter will remove this
515f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // conversation fragment on its own due to a separate
516f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // conversation cursor update (we might get here if the
517f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // message list update fires first. nothing to do
518f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // because we expect to be torn down soon.)
519f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        LogUtils.i(LOG_TAG, "CVF: offscreen conv has no messages, ignoring update"
520c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook                                + " in anticipation of conv cursor update. c=%s",
521c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook                                mConversation.uri);
522f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    }
523233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // existing mCursor will imminently be closed, must stop referencing it
524233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // since we expect to be kicked out soon, it doesn't matter what mCursor
525233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // becomes
526233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    mCursor = null;
527f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    return;
528f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
529f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
530f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // ignore cursors that are still loading results
531f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                if (!messageCursor.isLoaded()) {
532233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // existing mCursor will imminently be closed, must stop referencing it
533233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // in this case, the new cursor is also no good, and since don't expect to get
534233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // here except in initial load situations, it's safest to just ensure the
535233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // reference is null
536233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    mCursor = null;
537f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    return;
538f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
539014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                final MessageCursor oldCursor = mCursor;
5406766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang                mCursor = messageCursor;
541014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                onMessageCursorLoadFinished(loader, mCursor, oldCursor);
542f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            }
543f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
544f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
545f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
546c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook        public void onLoaderReset(Loader<ObjectCursor<ConversationMessage>>  loader) {
547f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mCursor = null;
548f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
549f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
550f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
551f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
552bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp    private void onError() {
553bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // need to exit this view- conversation may have been
554bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // deleted, or for whatever reason is now invalid (e.g.
555bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // discard single draft)
556bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        //
557bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // N.B. this may involve a fragment transaction, which
558bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // FragmentManager will refuse to execute directly
559bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // within onLoadFinished. Make sure the controller knows.
560bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        LogUtils.i(LOG_TAG, "CVF: visible conv has no messages, exiting conv mode");
561bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // TODO(mindyp): handle ERROR status by showing an error
562bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // message to the user that there are no messages in
563bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // this conversation
56418176786f320b4d4312ad1682da057434f201c13Scott Kennedy        popOut();
56518176786f320b4d4312ad1682da057434f201c13Scott Kennedy    }
566bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp
56718176786f320b4d4312ad1682da057434f201c13Scott Kennedy    private void popOut() {
568376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein        mHandler.post(new FragmentRunnable("popOut", this) {
569bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            @Override
5709a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            public void go() {
5717729a9aaa1873505191303a35c480b6dcf1c7382Alice Yang                if (mActivity != null) {
5727729a9aaa1873505191303a35c480b6dcf1c7382Alice Yang                    mActivity.getListHandler()
5737729a9aaa1873505191303a35c480b6dcf1c7382Alice Yang                            .onConversationSelected(null, true /* inLoaderCallbacks */);
5747729a9aaa1873505191303a35c480b6dcf1c7382Alice Yang                }
575bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            }
576bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        });
577bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp    }
578bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp
579e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang    /**
580e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang     * @see Folder#getTypeDescription()
581e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang     */
582e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang    protected String getCurrentFolderTypeDesc() {
583e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang        final Folder currFolder;
584e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang        if (mActivity != null) {
585e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang            currFolder = mActivity.getFolderController().getFolder();
586e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang        } else {
587e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang            currFolder = null;
588e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang        }
589e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang        final String folderStr;
590e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang        if (currFolder != null) {
591e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang            folderStr = currFolder.getTypeDescription();
592e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang        } else {
593e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang            folderStr = "unknown_folder";
594e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang        }
595e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang        return folderStr;
596e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang    }
597e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang
598e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang    private void logConversationView() {
599e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang      final String folderStr = getCurrentFolderTypeDesc();
600e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang      Analytics.getInstance().sendEvent("view_conversation", folderStr,
601e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang              mConversation.isRemote ? "unsynced" : "synced", mConversation.getNumMessages());
602e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang    }
603e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang
60487a8982af83168453b6dc219fe984bff68047bebAndy Huang    protected final void onConversationSeen() {
605919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy        LogUtils.d(LOG_TAG, "AbstractConversationViewFragment#onConversationSeen()");
606919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy
6074d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // Ignore unsafe calls made after a fragment is detached from an activity
6084d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        final ControllableActivity activity = (ControllableActivity) getActivity();
6094d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        if (activity == null) {
6104d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            LogUtils.w(LOG_TAG, "ignoring onConversationSeen for conv=%s", mConversation.id);
6114d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            return;
6124d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
6134d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
614e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang        // this method is called 2x on rotation; debounce this a bit so as not to
615e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang        // dramatically skew analytics data too much. Ideally, it should be called zero times
616e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang        // on rotation...
617e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang        if (!mConversationSeen) {
618e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang            logConversationView();
619e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang        }
620e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang
6214d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        mViewState.setInfoForConversation(mConversation);
6224d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
623919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy        LogUtils.d(LOG_TAG, "onConversationSeen() - mSuppressMarkingViewed = %b",
624919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy                mSuppressMarkingViewed);
625d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        // In most circumstances we want to mark the conversation as viewed and read, since the
626d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        // user has read it.  However, if the user has already marked the conversation unread, we
627d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        // do not want a  later mark-read operation to undo this.  So we check this variable which
628d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        // is set in #markUnread() which suppresses automatic mark-read.
629d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        if (!mSuppressMarkingViewed) {
63087a8982af83168453b6dc219fe984bff68047bebAndy Huang            markRead(false /* force */);
6314d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
6323b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        activity.getListHandler().onConversationSeen();
633e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang
634e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang        mConversationSeen = true;
6354d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    }
6364d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
6374d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    protected ConversationViewState getNewViewState() {
6384d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        return new ConversationViewState();
6394d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    }
6404d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
641c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook    private static class MessageLoader extends ObjectCursorLoader<ConversationMessage> {
642f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private boolean mDeliveredFirstResults = false;
643f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
64402133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang        public MessageLoader(Context c, Uri messageListUri) {
645c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook            super(c, messageListUri, UIProvider.MESSAGE_PROJECTION, ConversationMessage.FACTORY);
646f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
647f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
648f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
649c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook        public void deliverResult(ObjectCursor<ConversationMessage> result) {
650f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // We want to deliver these results, and then we want to make sure
651f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // that any subsequent
652f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // queries do not hit the network
653f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            super.deliverResult(result);
654f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
655f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (!mDeliveredFirstResults) {
656f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                mDeliveredFirstResults = true;
657f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                Uri uri = getUri();
658f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
659f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // Create a ListParams that tells the provider to not hit the
660f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // network
661f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                final ListParams listParams = new ListParams(ListParams.NO_LIMIT,
662f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        false /* useNetwork */);
663f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
664f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // Build the new uri with this additional parameter
665f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                uri = uri
666f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        .buildUpon()
667f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        .appendQueryParameter(UIProvider.LIST_PARAMS_QUERY_PARAMETER,
668f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                                listParams.serialize()).build();
669f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                setUri(uri);
670f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            }
671f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
672c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook
673c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook        @Override
674c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook        protected ObjectCursor<ConversationMessage> getObjectCursor(Cursor inner) {
675c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook            return new MessageCursor(inner);
676c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook        }
677f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
678f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
67926d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp    public abstract void onConversationUpdated(Conversation conversation);
68026d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp
68118176786f320b4d4312ad1682da057434f201c13Scott Kennedy    public void onDetachedModeEntered() {
68218176786f320b4d4312ad1682da057434f201c13Scott Kennedy        // If we have no messages, then we have nothing to display, so leave this view.
68318176786f320b4d4312ad1682da057434f201c13Scott Kennedy        // Otherwise, just set the detached flag.
68418176786f320b4d4312ad1682da057434f201c13Scott Kennedy        final Cursor messageCursor = getMessageCursor();
68518176786f320b4d4312ad1682da057434f201c13Scott Kennedy
68618176786f320b4d4312ad1682da057434f201c13Scott Kennedy        if (messageCursor == null || messageCursor.getCount() == 0) {
68718176786f320b4d4312ad1682da057434f201c13Scott Kennedy            popOut();
68818176786f320b4d4312ad1682da057434f201c13Scott Kennedy        } else {
68918176786f320b4d4312ad1682da057434f201c13Scott Kennedy            mIsDetached = true;
69018176786f320b4d4312ad1682da057434f201c13Scott Kennedy        }
69118176786f320b4d4312ad1682da057434f201c13Scott Kennedy    }
6922fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein
6932fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    /**
6942fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * Called when the JavaScript reports that it transformed a message.
6952fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * Sets a flag to true and invalidates the options menu so it will
6962fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * include the "Revert auto-sizing" menu option.
6972fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     */
6982fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    public void onConversationTransformed() {
6992fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein        mHasConversationBeenTransformed = true;
700376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein        mHandler.post(new FragmentRunnable("invalidateOptionsMenu", this) {
701ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein            @Override
702ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein            public void go() {
70352882ff08fe9420fe2dbb3f681545a330222e9c8Andrew Sapperstein                mActivity.supportInvalidateOptionsMenu();
704ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein            }
705ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein        });
7062fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    }
7072fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein
7082fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    /**
7092fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * Called when the "Revert auto-sizing" option is selected. Default
7102fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * implementation simply sets a value on whether transforms should be
7112fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * applied. Derived classes should override this class and force a
7122fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * re-render so that the conversation renders without
7132fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     */
7142fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    public void showUntransformedConversation() {
7152fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein        // must set the value to true so we don't show the options menu item again
7162fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein        mHasConversationTransformBeenReverted = true;
7172fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    }
7182fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein
7192fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    /**
7202fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * Returns {@code true} if the conversation should be transformed. {@code false}, otherwise.
7212fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     * @return {@code true} if the conversation should be transformed. {@code false}, otherwise.
7222fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein     */
7232fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    public boolean shouldApplyTransforms() {
7243617b41394337774fb2e6fcf9fef1ac9eca00482Alice Yang        return (mAccount.enableMessageTransforms > 0) &&
7253617b41394337774fb2e6fcf9fef1ac9eca00482Alice Yang                !mHasConversationTransformBeenReverted;
7262fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    }
7275c1692a5faeab220881a17a3427a8986ef874403Andrew Sapperstein
7287cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux    /**
7297cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux     * The Print item in the overflow menu of the Conversation view is shown based on the return
7307cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux     * from this method.
7317cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux     *
7327cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux     * @return {@code true} if the conversation can be printed; {@code false} otherwise.
7337cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux     */
7347cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux    protected abstract boolean shouldShowPrintInOverflow();
7357cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux
7367cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux    /**
7377cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux     * Prints all messages in the conversation.
7387cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux     */
7395c1692a5faeab220881a17a3427a8986ef874403Andrew Sapperstein    protected abstract void printConversation();
7408ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein
741a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao    // These methods should perform default reply/replyall action on the last message.
742a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao    protected abstract void handleReply();
743a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao    protected abstract void handleReplyAll();
744a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao
7458ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein    public boolean shouldAlwaysShowImages() {
7468ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein        return (mAccount != null) && (mAccount.settings.showImages == Settings.ShowImages.ALWAYS);
7478ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein    }
748f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp}
749