AbstractConversationViewFragment.java revision 9e4ca7993213d296043d20fe9cf4e166b5d595e8
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
20ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindypimport android.animation.Animator;
219d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huangimport android.animation.AnimatorInflater;
227d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huangimport android.animation.AnimatorListenerAdapter;
23f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.app.Activity;
24f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.app.Fragment;
25f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.app.LoaderManager;
26542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrookimport android.content.ActivityNotFoundException;
27f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.content.Context;
28f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.content.CursorLoader;
29542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrookimport android.content.Intent;
30f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.content.Loader;
31542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrookimport android.content.pm.ActivityInfo;
32542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrookimport android.content.pm.PackageManager;
33542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrookimport android.content.pm.ResolveInfo;
34ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindypimport android.content.res.Resources;
35f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.database.Cursor;
36f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.database.DataSetObservable;
37f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.database.DataSetObserver;
38f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.net.Uri;
39f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.os.Bundle;
40ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindypimport android.os.Handler;
41542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrookimport android.provider.Browser;
42f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.view.Menu;
43f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.view.MenuInflater;
44f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.view.MenuItem;
45ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindypimport android.view.View;
46542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrookimport android.webkit.WebView;
47542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrookimport android.webkit.WebViewClient;
48f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
49f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.ContactInfo;
50f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.ContactInfoSource;
51f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.FormattedDateBuilder;
52f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.R;
53f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.SenderInfoLoader;
548f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huangimport com.android.mail.browse.ConversationAccountController;
55f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.browse.ConversationViewHeader.ConversationViewHeaderCallbacks;
569d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huangimport com.android.mail.browse.MessageCursor;
57f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.browse.MessageCursor.ConversationController;
58f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.browse.MessageHeaderView.MessageHeaderViewCallbacks;
59f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.Account;
60f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.AccountObserver;
61f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.Address;
62f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.Conversation;
63f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.Folder;
64f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.ListParams;
65f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.UIProvider;
66a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwalimport com.android.mail.providers.UIProvider.CursorStatus;
67f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.LogTag;
68f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.LogUtils;
69f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.Utils;
70f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.google.common.collect.ImmutableMap;
71f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.google.common.collect.Maps;
72f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
734d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrookimport java.util.Arrays;
74542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrookimport java.util.List;
75f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport java.util.Map;
76f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport java.util.Set;
77f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
78f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyppublic abstract class AbstractConversationViewFragment extends Fragment implements
79f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        ConversationController, ConversationAccountController, MessageHeaderViewCallbacks,
80f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        ConversationViewHeaderCallbacks {
81f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
82f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private static final String ARG_ACCOUNT = "account";
83f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public static final String ARG_CONVERSATION = "conversation";
84f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private static final String ARG_FOLDER = "folder";
85f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private static final String LOG_TAG = LogTag.getLogTag();
86f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected static final int MESSAGE_LOADER = 0;
87f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected static final int CONTACT_LOADER = 1;
8832d911f491afad153983f7519267ddb764927355mindyp    private static int sMinDelay = -1;
8932d911f491afad153983f7519267ddb764927355mindyp    private static int sMinShowTime = -1;
90f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected ControllableActivity mActivity;
91f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private final MessageLoaderCallbacks mMessageLoaderCallbacks = new MessageLoaderCallbacks();
92f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected FormattedDateBuilder mDateBuilder;
93f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private final ContactLoaderCallbacks mContactLoaderCallbacks = new ContactLoaderCallbacks();
94f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private MenuItem mChangeFoldersMenuItem;
95f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected Conversation mConversation;
96f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected Folder mFolder;
97f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected String mBaseUri;
98f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected Account mAccount;
99f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected final Map<String, Address> mAddressCache = Maps.newHashMap();
100f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected boolean mEnableContentReadySignal;
101f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private MessageCursor mCursor;
102f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private Context mContext;
1039d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    /**
1049d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * A backwards-compatible version of {{@link #getUserVisibleHint()}. Like the framework flag,
1059d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * this flag is saved and restored.
1069d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     */
1079d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private boolean mUserVisible;
108ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    private View mProgressView;
109ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    private View mBackgroundView;
110ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    private final Handler mHandler = new Handler();
111d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal    /** True if we want to avoid marking the conversation as viewed and read. */
112d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal    private boolean mSuppressMarkingViewed;
1134d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    /**
1144d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook     * Parcelable state of the conversation view. Can safely be used without null checking any time
1159d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * after {@link #onCreate(Bundle)}.
1164d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook     */
1174d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    protected ConversationViewState mViewState;
1184d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
11932d911f491afad153983f7519267ddb764927355mindyp    private long mLoadingShownTime = -1;
12032d911f491afad153983f7519267ddb764927355mindyp
1219a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang    private final Runnable mDelayedShow = new FragmentRunnable("mDelayedShow") {
122ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        @Override
1239a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        public void go() {
12432d911f491afad153983f7519267ddb764927355mindyp            mLoadingShownTime = System.currentTimeMillis();
1252f2ad3f5ecc0185dfa09091062322dbaeb14c7edAndy Huang            mProgressView.setVisibility(View.VISIBLE);
126ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        }
127ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    };
128ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
129f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private final AccountObserver mAccountObserver = new AccountObserver() {
130f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
131f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void onChanged(Account newAccount) {
132adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            final Account oldAccount = mAccount;
133f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mAccount = newAccount;
134adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            onAccountChanged(newAccount, oldAccount);
135f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
136f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    };
137f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
1389d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private static final String BUNDLE_VIEW_STATE =
1399d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            AbstractConversationViewFragment.class.getName() + "viewstate";
1409a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang    /**
1419a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang     * We save the user visible flag so the various transitions that occur during rotation do not
1429a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang     * cause unnecessary visibility change.
1439a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang     */
1449d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private static final String BUNDLE_USER_VISIBLE =
1459d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            AbstractConversationViewFragment.class.getName() + "uservisible";
1469d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
147f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public static Bundle makeBasicArgs(Account account, Folder folder) {
148f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        Bundle args = new Bundle();
149f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        args.putParcelable(ARG_ACCOUNT, account);
150f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        args.putParcelable(ARG_FOLDER, folder);
151f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return args;
152f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
153f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
154f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
155f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Constructor needs to be public to handle orientation changes and activity
156f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * lifecycle events.
157f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
158f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public AbstractConversationViewFragment() {
159f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super();
160f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
161f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
162f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
163f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override, since this depends on how many messages are
164f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * shown in the conversation view.
165f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
166d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal    protected void markUnread() {
167d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        // Do not automatically mark this conversation viewed and read.
168d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        mSuppressMarkingViewed = true;
169d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal    }
170f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
171f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
172f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override this, since they may want to display a single or
173f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * many messages related to this conversation.
174f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
175014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang    protected abstract void onMessageCursorLoadFinished(Loader<Cursor> loader,
176014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            MessageCursor newCursor, MessageCursor oldCursor);
177f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
178f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
179f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override this, since they may want to display a single or
180f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * many messages related to this conversation.
181f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
182f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
183f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public abstract void onConversationViewHeaderHeightChange(int newHeight);
184f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
185f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public abstract void onUserVisibleHintChanged();
186f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
187f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
188f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override this.
189f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
190adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    protected abstract void onAccountChanged(Account newAccount, Account oldAccount);
191f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
192f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
193f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onCreate(Bundle savedState) {
194f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onCreate(savedState);
195f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
196f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        final Bundle args = getArguments();
197f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mAccount = args.getParcelable(ARG_ACCOUNT);
198f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mConversation = args.getParcelable(ARG_CONVERSATION);
199f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mFolder = args.getParcelable(ARG_FOLDER);
200ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook
201ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook        // Since the uri specified in the conversation base uri may not be unique, we specify a
202ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook        // base uri that us guaranteed to be unique for this conversation.
203ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook        mBaseUri = "x-thread://" + mAccount.name + "/" + mConversation.id;
204ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook
205f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // On JB or newer, we use the 'webkitAnimationStart' DOM event to signal load complete
206f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // Below JB, try to speed up initial render by having the webview do supplemental draws to
207f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // custom a software canvas.
208f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // TODO(mindyp):
209f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        //PAGE READINESS SIGNAL FOR JELLYBEAN AND NEWER
210f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // Notify the app on 'webkitAnimationStart' of a simple dummy element with a simple no-op
211f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // animation that immediately runs on page load. The app uses this as a signal that the
212f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // content is loaded and ready to draw, since WebView delays firing this event until the
213f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // layers are composited and everything is ready to draw.
214f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // This signal does not seem to be reliable, so just use the old method for now.
215f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mEnableContentReadySignal = false; //Utils.isRunningJellybeanOrLater();
216f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        LogUtils.d(LOG_TAG, "onCreate in ConversationViewFragment (this=%s)", this);
217f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // Not really, we just want to get a crack to store a reference to the change_folder item
218f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        setHasOptionsMenu(true);
2199d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
2209d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (savedState != null) {
2219d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mViewState = savedState.getParcelable(BUNDLE_VIEW_STATE);
2229d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mUserVisible = savedState.getBoolean(BUNDLE_USER_VISIBLE);
2239d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        } else {
2249d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mViewState = getNewViewState();
2259d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
226f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
227f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
2289e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang    @Override
2299e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang    public String toString() {
2309e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        // log extra info at DEBUG level or finer
2319e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        final String s = super.toString();
2329e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        if (!LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG) || mConversation == null) {
2339e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang            return s;
2349e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        }
2359e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        return "(" + s + " conv=" + mConversation + ")";
2369e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang    }
2379e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang
238fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy    protected abstract WebView getWebView();
239fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy
240ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    public void instantiateProgressIndicators(View rootView) {
241ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        mBackgroundView = rootView.findViewById(R.id.background_view);
242ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        mProgressView = rootView.findViewById(R.id.loading_progress);
243ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    }
244ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
245ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    protected void dismissLoadingStatus() {
2467d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang        dismissLoadingStatus(null);
2477d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    }
2487d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang
2497d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    /**
2507d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang     * Begin the fade-out animation to hide the Progress overlay, either immediately or after some
2517d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang     * timeout (to ensure that the progress minimum time elapses).
2527d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang     *
2537d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang     * @param doAfter an optional Runnable action to execute after the animation completes
2547d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang     */
2557d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    protected void dismissLoadingStatus(final Runnable doAfter) {
25632d911f491afad153983f7519267ddb764927355mindyp        if (mLoadingShownTime == -1) {
257ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp            // The runnable hasn't run yet, so just remove it.
258ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp            mHandler.removeCallbacks(mDelayedShow);
2597d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang            dismiss(doAfter);
260ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp            return;
261ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        }
26232d911f491afad153983f7519267ddb764927355mindyp        final long diff = Math.abs(System.currentTimeMillis() - mLoadingShownTime);
26332d911f491afad153983f7519267ddb764927355mindyp        if (diff > sMinShowTime) {
2647d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang            dismiss(doAfter);
26532d911f491afad153983f7519267ddb764927355mindyp        } else {
2669a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            mHandler.postDelayed(new FragmentRunnable("dismissLoadingStatus") {
2677d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                @Override
2689a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                public void go() {
2697d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                    dismiss(doAfter);
2707d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                }
2717d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang            }, Math.abs(sMinShowTime - diff));
27232d911f491afad153983f7519267ddb764927355mindyp        }
27332d911f491afad153983f7519267ddb764927355mindyp    }
27432d911f491afad153983f7519267ddb764927355mindyp
2757d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    private void dismiss(final Runnable doAfter) {
27632d911f491afad153983f7519267ddb764927355mindyp        // Reset loading shown time.
27732d911f491afad153983f7519267ddb764927355mindyp        mLoadingShownTime = -1;
2789a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        mProgressView.setVisibility(View.GONE);
279ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        if (mBackgroundView.getVisibility() == View.VISIBLE) {
2809a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            animateDismiss(doAfter);
281ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        } else {
28230bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang            if (doAfter != null) {
28330bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang                doAfter.run();
28430bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang            }
285ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        }
286ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    }
287ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
2889a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang    private void animateDismiss(final Runnable doAfter) {
2899a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        // the animation can only work (and is only worth doing) if this fragment is added
2909a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        // reasons it may not be added: fragment is being destroyed, or in the process of being
2919a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        // restored
2929a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        if (!isAdded()) {
2939a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            mBackgroundView.setVisibility(View.GONE);
2949a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            return;
2959a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        }
2969a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang
297bb6039ed45a5eeccf08d97cb91d1b91069fed5afAndy Huang        Utils.enableHardwareLayer(mBackgroundView);
2989a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        final Animator animator = AnimatorInflater.loadAnimator(getContext(), R.anim.fade_out);
2999a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        animator.setTarget(mBackgroundView);
3009a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        animator.addListener(new AnimatorListenerAdapter() {
3019a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            @Override
3029a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            public void onAnimationEnd(Animator animation) {
3039a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                mBackgroundView.setVisibility(View.GONE);
3049a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                mBackgroundView.setLayerType(View.LAYER_TYPE_NONE, null);
3059a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                if (doAfter != null) {
3069a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                    doAfter.run();
3079a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                }
3089a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            }
3099a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        });
3109a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        animator.start();
3119a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang    }
3129a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang
313f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
314f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onActivityCreated(Bundle savedInstanceState) {
315f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onActivityCreated(savedInstanceState);
3168fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal        final Activity activity = getActivity();
317f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (!(activity instanceof ControllableActivity)) {
318f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            LogUtils.wtf(LOG_TAG, "ConversationViewFragment expects only a ControllableActivity to"
319f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    + "create it. Cannot proceed.");
320f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
3218fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal        if (activity == null || activity.isFinishing()) {
322f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // Activity is finishing, just bail.
323f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return;
324f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
3258fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal        mActivity = (ControllableActivity) activity;
326f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mContext = activity.getApplicationContext();
327f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mDateBuilder = new FormattedDateBuilder((Context) mActivity);
328f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mAccount = mAccountObserver.initialize(mActivity.getAccountController());
329f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
330f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
331f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
332f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public ConversationUpdater getListController() {
333f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        final ControllableActivity activity = (ControllableActivity) getActivity();
334f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return activity != null ? activity.getConversationUpdater() : null;
335f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
336f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
337ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
338ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    protected void showLoadingStatus() {
33932d911f491afad153983f7519267ddb764927355mindyp        if (!mUserVisible) {
34032d911f491afad153983f7519267ddb764927355mindyp            return;
34132d911f491afad153983f7519267ddb764927355mindyp        }
342ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        if (sMinDelay == -1) {
34332d911f491afad153983f7519267ddb764927355mindyp            Resources res = getContext().getResources();
34432d911f491afad153983f7519267ddb764927355mindyp            sMinDelay = res.getInteger(R.integer.conversationview_show_loading_delay);
34532d911f491afad153983f7519267ddb764927355mindyp            sMinShowTime = res.getInteger(R.integer.conversationview_min_show_loading);
346ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        }
34732d911f491afad153983f7519267ddb764927355mindyp        // If the loading view isn't already showing, show it and remove any
34832d911f491afad153983f7519267ddb764927355mindyp        // pending calls to show the loading screen.
34932d911f491afad153983f7519267ddb764927355mindyp        mBackgroundView.setVisibility(View.VISIBLE);
350ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        mHandler.removeCallbacks(mDelayedShow);
351ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        mHandler.postDelayed(mDelayedShow, sMinDelay);
352ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    }
353ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
354f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public Context getContext() {
355f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mContext;
356f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
357f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
35802133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang    @Override
359f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public Conversation getConversation() {
360f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mConversation;
361f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
362f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
363f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
364f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public MessageCursor getMessageCursor() {
365f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mCursor;
366f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
367f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
368ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    public Handler getHandler() {
369ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        return mHandler;
370ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    }
371ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
372f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public MessageLoaderCallbacks getMessageLoaderCallbacks() {
373f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mMessageLoaderCallbacks;
374f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
375f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
376f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public ContactLoaderCallbacks getContactInfoSource() {
377f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mContactLoaderCallbacks;
378f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
379f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
380f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
381f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public Account getAccount() {
382f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mAccount;
383f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
384f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
385f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
386f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
387f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onCreateOptionsMenu(menu, inflater);
388f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mChangeFoldersMenuItem = menu.findItem(R.id.change_folder);
389f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
390f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
391f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
392f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public boolean onOptionsItemSelected(MenuItem item) {
393f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        boolean handled = false;
394f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        switch (item.getItemId()) {
395f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            case R.id.inside_conversation_unread:
396f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                markUnread();
397f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                handled = true;
398f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                break;
399f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
400f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return handled;
401f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
402f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
403f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    // BEGIN conversation header callbacks
404f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
405f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onFoldersClicked() {
406f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (mChangeFoldersMenuItem == null) {
407f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            LogUtils.e(LOG_TAG, "unable to open 'change folders' dialog for a conversation");
408f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return;
409f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
410f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mActivity.onOptionsItemSelected(mChangeFoldersMenuItem);
411f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
412f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
413f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
414f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public String getSubjectRemainder(String subject) {
415f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        final SubjectDisplayChanger sdc = mActivity.getSubjectDisplayChanger();
416f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (sdc == null) {
417f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return subject;
418f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
419f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return sdc.getUnshownSubject(subject);
420f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
421f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    // END conversation header callbacks
422f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
423f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
4249d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    public void onSaveInstanceState(Bundle outState) {
4259d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (mViewState != null) {
4269d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            outState.putParcelable(BUNDLE_VIEW_STATE, mViewState);
4279d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
4289d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        outState.putBoolean(BUNDLE_USER_VISIBLE, mUserVisible);
4299d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
4309d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
4319d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    @Override
432f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onDestroyView() {
433f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onDestroyView();
434f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mAccountObserver.unregisterAndDestroy();
435f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
436f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
437f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
438f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * {@link #setUserVisibleHint(boolean)} only works on API >= 15, so implement our own for
439f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * reliability on older platforms.
440f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
441f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void setExtraUserVisibleHint(boolean isVisibleToUser) {
442f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        LogUtils.v(LOG_TAG, "in CVF.setHint, val=%s (%s)", isVisibleToUser, this);
443f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (mUserVisible != isVisibleToUser) {
444f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mUserVisible = isVisibleToUser;
445bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            MessageCursor cursor = getMessageCursor();
4460b9b48c1ac510d6d0d043a86354880ceb57a8175mindyp            if (mUserVisible && (cursor != null && cursor.isLoaded() && cursor.getCount() == 0)) {
447bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                // Pop back to conversation list and show error.
448bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                onError();
449bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                return;
450bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            }
451f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            onUserVisibleHintChanged();
452f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
453f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
454f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
4559d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    public boolean isUserVisible() {
4569d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        return mUserVisible;
4579d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
4589d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
459f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private class MessageLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
460f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
461f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
462f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
46302133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang            return new MessageLoader(mActivity.getActivityContext(), mConversation.messageListUri);
464f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
465f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
466f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
467f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
468f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // ignore truly duplicate results
469f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // this can happen when restoring after rotation
470f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (mCursor == data) {
471f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                return;
472f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            } else {
4736766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang                final MessageCursor messageCursor = (MessageCursor) data;
474f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
47502133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang                // bind the cursor to this fragment so it can access to the current list controller
47602133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang                messageCursor.setController(AbstractConversationViewFragment.this);
47702133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang
478f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
479f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    LogUtils.d(LOG_TAG, "LOADED CONVERSATION= %s", messageCursor.getDebugDump());
480f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
481f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
482a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal                // We have no messages: exit conversation view.
483a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal                if (messageCursor.getCount() == 0
484c7523c17ba955b9a7c754edf6dbda7d010d06a30Andy Huang                        && !CursorStatus.isWaitingForResults(messageCursor.getStatus())) {
485f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    if (mUserVisible) {
486bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                        onError();
487f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    } else {
488f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // we expect that the pager adapter will remove this
489f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // conversation fragment on its own due to a separate
490f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // conversation cursor update (we might get here if the
491f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // message list update fires first. nothing to do
492f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // because we expect to be torn down soon.)
493f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        LogUtils.i(LOG_TAG, "CVF: offscreen conv has no messages, ignoring update"
494f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                                + " in anticipation of conv cursor update. c=%s", mConversation.uri);
495f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    }
496233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // existing mCursor will imminently be closed, must stop referencing it
497233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // since we expect to be kicked out soon, it doesn't matter what mCursor
498233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // becomes
499233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    mCursor = null;
500f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    return;
501f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
502f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
503f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // ignore cursors that are still loading results
504f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                if (!messageCursor.isLoaded()) {
505233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // existing mCursor will imminently be closed, must stop referencing it
506233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // in this case, the new cursor is also no good, and since don't expect to get
507233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // here except in initial load situations, it's safest to just ensure the
508233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // reference is null
509233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    mCursor = null;
510f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    return;
511f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
512014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                final MessageCursor oldCursor = mCursor;
5136766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang                mCursor = messageCursor;
514014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                onMessageCursorLoadFinished(loader, mCursor, oldCursor);
515f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            }
516f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
517f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
518f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
519f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void onLoaderReset(Loader<Cursor> loader) {
520f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mCursor = null;
521f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
522f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
523f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
524f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
525bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp    private void onError() {
526bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // need to exit this view- conversation may have been
527bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // deleted, or for whatever reason is now invalid (e.g.
528bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // discard single draft)
529bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        //
530bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // N.B. this may involve a fragment transaction, which
531bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // FragmentManager will refuse to execute directly
532bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // within onLoadFinished. Make sure the controller knows.
533bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        LogUtils.i(LOG_TAG, "CVF: visible conv has no messages, exiting conv mode");
534bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // TODO(mindyp): handle ERROR status by showing an error
535bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // message to the user that there are no messages in
536bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // this conversation
53718176786f320b4d4312ad1682da057434f201c13Scott Kennedy        popOut();
53818176786f320b4d4312ad1682da057434f201c13Scott Kennedy    }
539bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp
54018176786f320b4d4312ad1682da057434f201c13Scott Kennedy    private void popOut() {
54118176786f320b4d4312ad1682da057434f201c13Scott Kennedy        mHandler.post(new FragmentRunnable("popOut") {
542bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            @Override
5439a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            public void go() {
544bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                mActivity.getListHandler()
545bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                .onConversationSelected(null, true /* inLoaderCallbacks */);
546bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            }
547bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        });
548bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp    }
549bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp
5504d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    protected void onConversationSeen() {
5514d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // Ignore unsafe calls made after a fragment is detached from an activity
5524d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        final ControllableActivity activity = (ControllableActivity) getActivity();
5534d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        if (activity == null) {
5544d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            LogUtils.w(LOG_TAG, "ignoring onConversationSeen for conv=%s", mConversation.id);
5554d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            return;
5564d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
5574d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
5584d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        mViewState.setInfoForConversation(mConversation);
5594d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
560d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        // In most circumstances we want to mark the conversation as viewed and read, since the
561d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        // user has read it.  However, if the user has already marked the conversation unread, we
562d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        // do not want a  later mark-read operation to undo this.  So we check this variable which
563d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        // is set in #markUnread() which suppresses automatic mark-read.
564d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        if (!mSuppressMarkingViewed) {
565d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal            // mark viewed/read if not previously marked viewed by this conversation view,
566d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal            // or if unread messages still exist in the message list cursor
567d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal            // we don't want to keep marking viewed on rotation or restore
568d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal            // but we do want future re-renders to mark read (e.g. "New message from X" case)
569d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal            MessageCursor cursor = getMessageCursor();
570d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal            if (!mConversation.isViewed() || (cursor != null && !cursor.isConversationRead())) {
571d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal                // Mark the conversation viewed and read.
572d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal                activity.getConversationUpdater()
573d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal                        .markConversationsRead(Arrays.asList(mConversation), true, true);
574d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal
575d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal                // and update the Message objects in the cursor so the next time a cursor update
576d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal                // happens with these messages marked read, we know to ignore it
577d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal                if (cursor != null) {
578d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal                    cursor.markMessagesRead();
579d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal                }
5804d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            }
5814d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
5824d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        activity.getListHandler().onConversationSeen(mConversation);
583fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy
584fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy        showAutoFitPrompt();
5854d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    }
5864d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
5874d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    protected ConversationViewState getNewViewState() {
5884d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        return new ConversationViewState();
5894d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    }
5904d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
591f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private static class MessageLoader extends CursorLoader {
592f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private boolean mDeliveredFirstResults = false;
593f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
59402133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang        public MessageLoader(Context c, Uri messageListUri) {
59502133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang            super(c, messageListUri, UIProvider.MESSAGE_PROJECTION, null, null, null);
596f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
597f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
598f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
599f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public Cursor loadInBackground() {
60002133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang            return new MessageCursor(super.loadInBackground());
601f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
602f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
603f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
604f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void deliverResult(Cursor result) {
605f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // We want to deliver these results, and then we want to make sure
606f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // that any subsequent
607f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // queries do not hit the network
608f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            super.deliverResult(result);
609f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
610f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (!mDeliveredFirstResults) {
611f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                mDeliveredFirstResults = true;
612f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                Uri uri = getUri();
613f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
614f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // Create a ListParams that tells the provider to not hit the
615f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // network
616f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                final ListParams listParams = new ListParams(ListParams.NO_LIMIT,
617f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        false /* useNetwork */);
618f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
619f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // Build the new uri with this additional parameter
620f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                uri = uri
621f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        .buildUpon()
622f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        .appendQueryParameter(UIProvider.LIST_PARAMS_QUERY_PARAMETER,
623f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                                listParams.serialize()).build();
624f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                setUri(uri);
625f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            }
626f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
627f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
628f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
629f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
630f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Inner class to to asynchronously load contact data for all senders in the conversation,
631f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * and notify observers when the data is ready.
632f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     *
633f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
634f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected class ContactLoaderCallbacks implements ContactInfoSource,
635f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            LoaderManager.LoaderCallbacks<ImmutableMap<String, ContactInfo>> {
636f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
637f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private Set<String> mSenders;
638f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private ImmutableMap<String, ContactInfo> mContactInfoMap;
639f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private DataSetObservable mObservable = new DataSetObservable();
640f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
641f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void setSenders(Set<String> emailAddresses) {
642f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mSenders = emailAddresses;
643f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
644f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
645f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
646f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public Loader<ImmutableMap<String, ContactInfo>> onCreateLoader(int id, Bundle args) {
647f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return new SenderInfoLoader(mActivity.getActivityContext(), mSenders);
648f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
649f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
650f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
651f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void onLoadFinished(Loader<ImmutableMap<String, ContactInfo>> loader,
652f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                ImmutableMap<String, ContactInfo> data) {
653f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mContactInfoMap = data;
654f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mObservable.notifyChanged();
655f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
656f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
657f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
658f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void onLoaderReset(Loader<ImmutableMap<String, ContactInfo>> loader) {
659f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
660f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
661f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
662f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public ContactInfo getContactInfo(String email) {
663f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (mContactInfoMap == null) {
664f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                return null;
665f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            }
666f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return mContactInfoMap.get(email);
667f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
668f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
669f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
670f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void registerObserver(DataSetObserver observer) {
671f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mObservable.registerObserver(observer);
672f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
673f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
674f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
675f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void unregisterObserver(DataSetObserver observer) {
676f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mObservable.unregisterObserver(observer);
677f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
678542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook    }
679542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
680542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook    protected class AbstractConversationWebViewClient extends WebViewClient {
681542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook        @Override
682542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook        public boolean shouldOverrideUrlLoading(WebView view, String url) {
683542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            final Activity activity = getActivity();
684542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            if (activity == null) {
685542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                return false;
686542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            }
687542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
688542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            boolean result = false;
689542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            final Intent intent;
690542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            Uri uri = Uri.parse(url);
691542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            if (!Utils.isEmpty(mAccount.viewIntentProxyUri)) {
692542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                intent = generateProxyIntent(uri);
693542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            } else {
694542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                intent = new Intent(Intent.ACTION_VIEW, uri);
695542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                intent.putExtra(Browser.EXTRA_APPLICATION_ID, activity.getPackageName());
696542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            }
697f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
698542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            try {
699542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
700542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                activity.startActivity(intent);
701542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                result = true;
702542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            } catch (ActivityNotFoundException ex) {
703542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // If no application can handle the URL, assume that the
704542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // caller can handle it.
705542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            }
706542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
707542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            return result;
708542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook        }
709542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
710542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook        private Intent generateProxyIntent(Uri uri) {
711542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            final Intent intent = new Intent(Intent.ACTION_VIEW, mAccount.viewIntentProxyUri);
712542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            intent.putExtra(UIProvider.ViewProxyExtras.EXTRA_ORIGINAL_URI, uri);
713542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            intent.putExtra(UIProvider.ViewProxyExtras.EXTRA_ACCOUNT, mAccount);
714542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
715542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            final Context context = getContext();
716542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            PackageManager manager = null;
717542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            // We need to catch the exception to make CanvasConversationHeaderView
718542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            // test pass.  Bug: http://b/issue?id=3470653.
719542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            try {
720542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                manager = context.getPackageManager();
721542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            } catch (UnsupportedOperationException e) {
722542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                LogUtils.e(LOG_TAG, e, "Error getting package manager");
723542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            }
724542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
725542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            if (manager != null) {
726542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // Try and resolve the intent, to find an activity from this package
727542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                final List<ResolveInfo> resolvedActivities = manager.queryIntentActivities(
728542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                        intent, PackageManager.MATCH_DEFAULT_ONLY);
729542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
730542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                final String packageName = context.getPackageName();
731542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
732542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // Now try and find one that came from this package, if one is not found, the UI
733542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // provider must have specified an intent that is to be handled by a different apk.
734542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // In that case, the class name will not be set on the intent, so the default
735542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // intent resolution will be used.
736542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                for (ResolveInfo resolveInfo: resolvedActivities) {
737542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                    final ActivityInfo activityInfo = resolveInfo.activityInfo;
738542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                    if (packageName.equals(activityInfo.packageName)) {
739542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                        intent.setClassName(activityInfo.packageName, activityInfo.name);
740542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                        break;
741542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                    }
742542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                }
743542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            }
744542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
745542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            return intent;
746542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook        }
747f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
748542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
74926d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp    public abstract void onConversationUpdated(Conversation conversation);
75026d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp
7519d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    /**
7529d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * Small Runnable-like wrapper that first checks that the Fragment is in a good state before
7539d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * doing any work. Ideal for use with a {@link Handler}.
7549d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     */
7559d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    protected abstract class FragmentRunnable implements Runnable {
7569d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
7579d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        private final String mOpName;
7589d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
7599d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        public FragmentRunnable(String opName) {
7609d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mOpName = opName;
7619d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
7629d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
7639d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        public abstract void go();
7649d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
7659d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        @Override
7669d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        public void run() {
7679d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            if (!isAdded()) {
7689d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                LogUtils.i(LOG_TAG, "Unable to run op='%s' b/c fragment is not attached: %s",
7699d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                        mOpName, AbstractConversationViewFragment.this);
7709d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                return;
7719d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            }
7729d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            go();
7739d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
7749d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
7759d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
7769d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
777fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy    private static boolean isConversationViewModeSet(final Account acct) {
778fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy        return acct.settings.conversationViewMode != UIProvider.ConversationViewMode.UNDEFINED;
779fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy    }
780fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy
781fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy    private void showAutoFitPrompt() {
782fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy        // If the user has never set a conversation view mode, and they view a wide message, we
783fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy        // should prompt them to turn on auto-fit
784fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy        final boolean enablePrompt =
785fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy                getResources().getInteger(R.integer.prompt_auto_fit_on_first_wide_message) == 1;
786fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy        // TODO: Enable this dialog for Email and ensure it saves the setting properly, and remove
787fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy        // R.integer.prompt_auto_fit_on_first_wide_message
788fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy        if (enablePrompt && isUserVisible() && !isConversationViewModeSet(mAccount)) {
789fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy            final boolean isWideContent =
790fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy                    getWebView().canScrollHorizontally(1) || getWebView().canScrollHorizontally(-1);
791fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy
792fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy            final boolean dialogShowing =
793fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy                    getFragmentManager().findFragmentByTag(AutoFitPromptDialogFragment.FRAGMENT_TAG)
794fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy                    != null;
795fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy
796fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy            if (isWideContent && !dialogShowing) {
797fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy                // Not everything fits, so let's prompt them to set an auto-fit value
798fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy                AutoFitPromptDialogFragment.newInstance(mAccount.updateSettingsUri)
799fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy                        .show(getFragmentManager(), AutoFitPromptDialogFragment.FRAGMENT_TAG);
800fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy            }
801fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy        }
802fafcd17adae9b7ba5c270611d0f53608ced92e2eScott Kennedy    }
80318176786f320b4d4312ad1682da057434f201c13Scott Kennedy
804f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp}
805