AbstractConversationViewFragment.java revision 9a8bc1ead816f0398bdbeac1e744ed458028b5c4
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;
54f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.browse.ConversationViewAdapter.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;
66f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.LogTag;
67f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.LogUtils;
68f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.Utils;
69f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.google.common.collect.ImmutableMap;
70f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.google.common.collect.Maps;
71f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
724d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrookimport java.util.Arrays;
73542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrookimport java.util.List;
74f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport java.util.Map;
75f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport java.util.Set;
76f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
77f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyppublic abstract class AbstractConversationViewFragment extends Fragment implements
78f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        ConversationController, ConversationAccountController, MessageHeaderViewCallbacks,
79f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        ConversationViewHeaderCallbacks {
80f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
81f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private static final String ARG_ACCOUNT = "account";
82f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public static final String ARG_CONVERSATION = "conversation";
83f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private static final String ARG_FOLDER = "folder";
84f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private static final String LOG_TAG = LogTag.getLogTag();
85f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected static final int MESSAGE_LOADER = 0;
86f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected static final int CONTACT_LOADER = 1;
8732d911f491afad153983f7519267ddb764927355mindyp    private static int sMinDelay = -1;
8832d911f491afad153983f7519267ddb764927355mindyp    private static int sMinShowTime = -1;
89f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected ControllableActivity mActivity;
90f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private final MessageLoaderCallbacks mMessageLoaderCallbacks = new MessageLoaderCallbacks();
91f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected FormattedDateBuilder mDateBuilder;
92f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private final ContactLoaderCallbacks mContactLoaderCallbacks = new ContactLoaderCallbacks();
93f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private MenuItem mChangeFoldersMenuItem;
94f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected Conversation mConversation;
95f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected Folder mFolder;
96f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected String mBaseUri;
97f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected Account mAccount;
98f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected final Map<String, Address> mAddressCache = Maps.newHashMap();
99f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected boolean mEnableContentReadySignal;
100f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private MessageCursor mCursor;
101f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private Context mContext;
1029d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    /**
1039d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * A backwards-compatible version of {{@link #getUserVisibleHint()}. Like the framework flag,
1049d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * this flag is saved and restored.
1059d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     */
1069d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private boolean mUserVisible;
107ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    private View mProgressView;
108ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    private View mBackgroundView;
109ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    private final Handler mHandler = new Handler();
1104d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
1114d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    /**
1124d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook     * Parcelable state of the conversation view. Can safely be used without null checking any time
1139d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * after {@link #onCreate(Bundle)}.
1144d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook     */
1154d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    protected ConversationViewState mViewState;
1164d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
1174d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    /**
1184d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook     * Handles a deferred 'mark read' operation, necessary when the conversation view has finished
1194d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook     * loading before the conversation cursor. Normally null unless this situation occurs.
1204d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook     * When finally able to 'mark read', this observer will also be unregistered and cleaned up.
1214d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook     */
1224d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    private MarkReadObserver mMarkReadObserver;
1234d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
12432d911f491afad153983f7519267ddb764927355mindyp    private long mLoadingShownTime = -1;
12532d911f491afad153983f7519267ddb764927355mindyp
1269a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang    private final Runnable mDelayedShow = new FragmentRunnable("mDelayedShow") {
127ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        @Override
1289a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        public void go() {
12932d911f491afad153983f7519267ddb764927355mindyp            mLoadingShownTime = System.currentTimeMillis();
1302f2ad3f5ecc0185dfa09091062322dbaeb14c7edAndy Huang            mProgressView.setVisibility(View.VISIBLE);
131ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        }
132ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    };
133ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
134f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private final AccountObserver mAccountObserver = new AccountObserver() {
135f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
136f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void onChanged(Account newAccount) {
137adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            final Account oldAccount = mAccount;
138f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mAccount = newAccount;
139adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            onAccountChanged(newAccount, oldAccount);
140f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
141f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    };
142f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
1439d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private static final String BUNDLE_VIEW_STATE =
1449d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            AbstractConversationViewFragment.class.getName() + "viewstate";
1459a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang    /**
1469a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang     * We save the user visible flag so the various transitions that occur during rotation do not
1479a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang     * cause unnecessary visibility change.
1489a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang     */
1499d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private static final String BUNDLE_USER_VISIBLE =
1509d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            AbstractConversationViewFragment.class.getName() + "uservisible";
1519d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
152f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public static Bundle makeBasicArgs(Account account, Folder folder) {
153f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        Bundle args = new Bundle();
154f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        args.putParcelable(ARG_ACCOUNT, account);
155f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        args.putParcelable(ARG_FOLDER, folder);
156f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return args;
157f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
158f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
159f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
160f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Constructor needs to be public to handle orientation changes and activity
161f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * lifecycle events.
162f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
163f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public AbstractConversationViewFragment() {
164f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super();
165f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
166f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
167f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
168f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override, since this depends on how many messages are
169f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * shown in the conversation view.
170f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
171f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected abstract void markUnread();
172f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
173f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
174f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override this, since they may want to display a single or
175f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * many messages related to this conversation.
176f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
177014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang    protected abstract void onMessageCursorLoadFinished(Loader<Cursor> loader,
178014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            MessageCursor newCursor, MessageCursor oldCursor);
179f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
180f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
181f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override this, since they may want to display a single or
182f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * many messages related to this conversation.
183f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
184f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
185f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public abstract void onConversationViewHeaderHeightChange(int newHeight);
186f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
187f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public abstract void onUserVisibleHintChanged();
188f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
189f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
190f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override this.
191f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
192adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    protected abstract void onAccountChanged(Account newAccount, Account oldAccount);
193f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
194f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
195f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onCreate(Bundle savedState) {
196f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onCreate(savedState);
197f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
198f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        final Bundle args = getArguments();
199f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mAccount = args.getParcelable(ARG_ACCOUNT);
200f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mConversation = args.getParcelable(ARG_CONVERSATION);
201f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mFolder = args.getParcelable(ARG_FOLDER);
202ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook
203ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook        // Since the uri specified in the conversation base uri may not be unique, we specify a
204ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook        // base uri that us guaranteed to be unique for this conversation.
205ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook        mBaseUri = "x-thread://" + mAccount.name + "/" + mConversation.id;
206ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook
207f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // On JB or newer, we use the 'webkitAnimationStart' DOM event to signal load complete
208f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // Below JB, try to speed up initial render by having the webview do supplemental draws to
209f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // custom a software canvas.
210f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // TODO(mindyp):
211f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        //PAGE READINESS SIGNAL FOR JELLYBEAN AND NEWER
212f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // Notify the app on 'webkitAnimationStart' of a simple dummy element with a simple no-op
213f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // animation that immediately runs on page load. The app uses this as a signal that the
214f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // content is loaded and ready to draw, since WebView delays firing this event until the
215f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // layers are composited and everything is ready to draw.
216f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // This signal does not seem to be reliable, so just use the old method for now.
217f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mEnableContentReadySignal = false; //Utils.isRunningJellybeanOrLater();
218f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        LogUtils.d(LOG_TAG, "onCreate in ConversationViewFragment (this=%s)", this);
219f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // Not really, we just want to get a crack to store a reference to the change_folder item
220f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        setHasOptionsMenu(true);
2219d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
2229d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (savedState != null) {
2239d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mViewState = savedState.getParcelable(BUNDLE_VIEW_STATE);
2249d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mUserVisible = savedState.getBoolean(BUNDLE_USER_VISIBLE);
2259d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        } else {
2269d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mViewState = getNewViewState();
2279d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
228f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
229f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
230ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    public void instantiateProgressIndicators(View rootView) {
231ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        mBackgroundView = rootView.findViewById(R.id.background_view);
232ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        mProgressView = rootView.findViewById(R.id.loading_progress);
233ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    }
234ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
235ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    protected void dismissLoadingStatus() {
2367d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang        dismissLoadingStatus(null);
2377d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    }
2387d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang
2397d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    /**
2407d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang     * Begin the fade-out animation to hide the Progress overlay, either immediately or after some
2417d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang     * timeout (to ensure that the progress minimum time elapses).
2427d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang     *
2437d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang     * @param doAfter an optional Runnable action to execute after the animation completes
2447d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang     */
2457d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    protected void dismissLoadingStatus(final Runnable doAfter) {
24632d911f491afad153983f7519267ddb764927355mindyp        if (mLoadingShownTime == -1) {
247ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp            // The runnable hasn't run yet, so just remove it.
248ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp            mHandler.removeCallbacks(mDelayedShow);
2497d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang            dismiss(doAfter);
250ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp            return;
251ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        }
25232d911f491afad153983f7519267ddb764927355mindyp        final long diff = Math.abs(System.currentTimeMillis() - mLoadingShownTime);
25332d911f491afad153983f7519267ddb764927355mindyp        if (diff > sMinShowTime) {
2547d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang            dismiss(doAfter);
25532d911f491afad153983f7519267ddb764927355mindyp        } else {
2569a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            mHandler.postDelayed(new FragmentRunnable("dismissLoadingStatus") {
2577d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                @Override
2589a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                public void go() {
2597d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                    dismiss(doAfter);
2607d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                }
2617d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang            }, Math.abs(sMinShowTime - diff));
26232d911f491afad153983f7519267ddb764927355mindyp        }
26332d911f491afad153983f7519267ddb764927355mindyp    }
26432d911f491afad153983f7519267ddb764927355mindyp
2657d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    private void dismiss(final Runnable doAfter) {
26632d911f491afad153983f7519267ddb764927355mindyp        // Reset loading shown time.
26732d911f491afad153983f7519267ddb764927355mindyp        mLoadingShownTime = -1;
2689a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        mProgressView.setVisibility(View.GONE);
269ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        if (mBackgroundView.getVisibility() == View.VISIBLE) {
2709a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            animateDismiss(doAfter);
271ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        } else {
27230bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang            if (doAfter != null) {
27330bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang                doAfter.run();
27430bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang            }
275ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        }
276ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    }
277ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
2789a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang    private void animateDismiss(final Runnable doAfter) {
2799a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        // the animation can only work (and is only worth doing) if this fragment is added
2809a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        // reasons it may not be added: fragment is being destroyed, or in the process of being
2819a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        // restored
2829a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        if (!isAdded()) {
2839a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            mBackgroundView.setVisibility(View.GONE);
2849a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            return;
2859a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        }
2869a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang
2879a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        mBackgroundView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2889a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        mBackgroundView.buildLayer();
2899a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        final Animator animator = AnimatorInflater.loadAnimator(getContext(), R.anim.fade_out);
2909a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        animator.setTarget(mBackgroundView);
2919a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        animator.addListener(new AnimatorListenerAdapter() {
2929a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            @Override
2939a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            public void onAnimationEnd(Animator animation) {
2949a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                mBackgroundView.setVisibility(View.GONE);
2959a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                mBackgroundView.setLayerType(View.LAYER_TYPE_NONE, null);
2969a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                if (doAfter != null) {
2979a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                    doAfter.run();
2989a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                }
2999a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            }
3009a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        });
3019a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        animator.start();
3029a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang    }
3039a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang
304f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
305f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onActivityCreated(Bundle savedInstanceState) {
306f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onActivityCreated(savedInstanceState);
3078fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal        final Activity activity = getActivity();
308f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (!(activity instanceof ControllableActivity)) {
309f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            LogUtils.wtf(LOG_TAG, "ConversationViewFragment expects only a ControllableActivity to"
310f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    + "create it. Cannot proceed.");
311f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
3128fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal        if (activity == null || activity.isFinishing()) {
313f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // Activity is finishing, just bail.
314f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return;
315f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
3168fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal        mActivity = (ControllableActivity) activity;
317f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mContext = activity.getApplicationContext();
318f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mDateBuilder = new FormattedDateBuilder((Context) mActivity);
319f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mAccount = mAccountObserver.initialize(mActivity.getAccountController());
320f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
321f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
322f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
323f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public ConversationUpdater getListController() {
324f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        final ControllableActivity activity = (ControllableActivity) getActivity();
325f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return activity != null ? activity.getConversationUpdater() : null;
326f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
327f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
328ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
329ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    protected void showLoadingStatus() {
33032d911f491afad153983f7519267ddb764927355mindyp        if (!mUserVisible) {
33132d911f491afad153983f7519267ddb764927355mindyp            return;
33232d911f491afad153983f7519267ddb764927355mindyp        }
333ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        if (sMinDelay == -1) {
33432d911f491afad153983f7519267ddb764927355mindyp            Resources res = getContext().getResources();
33532d911f491afad153983f7519267ddb764927355mindyp            sMinDelay = res.getInteger(R.integer.conversationview_show_loading_delay);
33632d911f491afad153983f7519267ddb764927355mindyp            sMinShowTime = res.getInteger(R.integer.conversationview_min_show_loading);
337ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        }
33832d911f491afad153983f7519267ddb764927355mindyp        // If the loading view isn't already showing, show it and remove any
33932d911f491afad153983f7519267ddb764927355mindyp        // pending calls to show the loading screen.
34032d911f491afad153983f7519267ddb764927355mindyp        mBackgroundView.setVisibility(View.VISIBLE);
341ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        mHandler.removeCallbacks(mDelayedShow);
342ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        mHandler.postDelayed(mDelayedShow, sMinDelay);
343ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    }
344ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
345f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public Context getContext() {
346f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mContext;
347f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
348f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
349f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public Conversation getConversation() {
350f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mConversation;
351f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
352f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
353f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
354f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public MessageCursor getMessageCursor() {
355f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mCursor;
356f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
357f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
358ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    public Handler getHandler() {
359ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        return mHandler;
360ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    }
361ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
362f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public MessageLoaderCallbacks getMessageLoaderCallbacks() {
363f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mMessageLoaderCallbacks;
364f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
365f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
366f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public ContactLoaderCallbacks getContactInfoSource() {
367f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mContactLoaderCallbacks;
368f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
369f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
370f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
371f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public Account getAccount() {
372f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mAccount;
373f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
374f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
375f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
376f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
377f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onCreateOptionsMenu(menu, inflater);
378f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mChangeFoldersMenuItem = menu.findItem(R.id.change_folder);
379f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
380f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
381f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
382f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public boolean onOptionsItemSelected(MenuItem item) {
383f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        boolean handled = false;
384f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        switch (item.getItemId()) {
385f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            case R.id.inside_conversation_unread:
386f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                markUnread();
387f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                handled = true;
388f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                break;
389f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
390f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return handled;
391f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
392f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
393f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    // BEGIN conversation header callbacks
394f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
395f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onFoldersClicked() {
396f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (mChangeFoldersMenuItem == null) {
397f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            LogUtils.e(LOG_TAG, "unable to open 'change folders' dialog for a conversation");
398f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return;
399f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
400f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mActivity.onOptionsItemSelected(mChangeFoldersMenuItem);
401f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
402f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
403f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
404f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public String getSubjectRemainder(String subject) {
405f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        final SubjectDisplayChanger sdc = mActivity.getSubjectDisplayChanger();
406f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (sdc == null) {
407f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return subject;
408f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
409f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return sdc.getUnshownSubject(subject);
410f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
411f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    // END conversation header callbacks
412f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
413f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
4149d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    public void onSaveInstanceState(Bundle outState) {
4159d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (mViewState != null) {
4169d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            outState.putParcelable(BUNDLE_VIEW_STATE, mViewState);
4179d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
4189d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        outState.putBoolean(BUNDLE_USER_VISIBLE, mUserVisible);
4199d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
4209d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
4219d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    @Override
422f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onDestroyView() {
423f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onDestroyView();
424f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mAccountObserver.unregisterAndDestroy();
4254d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        if (mMarkReadObserver != null) {
4264d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            mActivity.getConversationUpdater().unregisterConversationListObserver(
4274d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                    mMarkReadObserver);
4284d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            mMarkReadObserver = null;
4294d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
430f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
431f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
432f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
433f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * {@link #setUserVisibleHint(boolean)} only works on API >= 15, so implement our own for
434f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * reliability on older platforms.
435f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
436f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void setExtraUserVisibleHint(boolean isVisibleToUser) {
437f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        LogUtils.v(LOG_TAG, "in CVF.setHint, val=%s (%s)", isVisibleToUser, this);
438f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (mUserVisible != isVisibleToUser) {
439f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mUserVisible = isVisibleToUser;
440bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            MessageCursor cursor = getMessageCursor();
4410b9b48c1ac510d6d0d043a86354880ceb57a8175mindyp            if (mUserVisible && (cursor != null && cursor.isLoaded() && cursor.getCount() == 0)) {
442bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                // Pop back to conversation list and show error.
443bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                onError();
444bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                return;
445bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            }
446f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            onUserVisibleHintChanged();
447f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
448f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
449f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
4509d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    public boolean isUserVisible() {
4519d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        return mUserVisible;
4529d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
4539d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
454f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private class MessageLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
455f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
456f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
457f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
458f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return new MessageLoader(mActivity.getActivityContext(), mConversation,
459f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    AbstractConversationViewFragment.this);
460f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
461f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
462f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
463f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
464f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // ignore truly duplicate results
465f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // this can happen when restoring after rotation
466f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (mCursor == data) {
467f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                return;
468f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            } else {
4696766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang                final MessageCursor messageCursor = (MessageCursor) data;
470f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
471f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
472f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    LogUtils.d(LOG_TAG, "LOADED CONVERSATION= %s", messageCursor.getDebugDump());
473f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
474f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
475f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // When the last cursor had message(s), and the new version has
476f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // no messages, we need to exit conversation view.
477f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                if (messageCursor.getCount() == 0 && mCursor != null) {
478f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    if (mUserVisible) {
479bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                        onError();
480f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    } else {
481f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // we expect that the pager adapter will remove this
482f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // conversation fragment on its own due to a separate
483f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // conversation cursor update (we might get here if the
484f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // message list update fires first. nothing to do
485f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // because we expect to be torn down soon.)
486f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        LogUtils.i(LOG_TAG, "CVF: offscreen conv has no messages, ignoring update"
487f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                                + " in anticipation of conv cursor update. c=%s", mConversation.uri);
488f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    }
489233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // existing mCursor will imminently be closed, must stop referencing it
490233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // since we expect to be kicked out soon, it doesn't matter what mCursor
491233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // becomes
492233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    mCursor = null;
493f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    return;
494f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
495f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
496f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // ignore cursors that are still loading results
497f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                if (!messageCursor.isLoaded()) {
498233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // existing mCursor will imminently be closed, must stop referencing it
499233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // in this case, the new cursor is also no good, and since don't expect to get
500233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // here except in initial load situations, it's safest to just ensure the
501233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // reference is null
502233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    mCursor = null;
503f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    return;
504f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
505014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                final MessageCursor oldCursor = mCursor;
5066766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang                mCursor = messageCursor;
507014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                onMessageCursorLoadFinished(loader, mCursor, oldCursor);
508f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            }
509f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
510f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
511f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
512f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void onLoaderReset(Loader<Cursor> loader) {
513f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mCursor = null;
514f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
515f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
516f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
517f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
518bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp    private void onError() {
519bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // need to exit this view- conversation may have been
520bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // deleted, or for whatever reason is now invalid (e.g.
521bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // discard single draft)
522bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        //
523bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // N.B. this may involve a fragment transaction, which
524bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // FragmentManager will refuse to execute directly
525bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // within onLoadFinished. Make sure the controller knows.
526bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        LogUtils.i(LOG_TAG, "CVF: visible conv has no messages, exiting conv mode");
527bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // TODO(mindyp): handle ERROR status by showing an error
528bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // message to the user that there are no messages in
529bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // this conversation
5309a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang        mHandler.post(new FragmentRunnable("onError") {
531bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp
532bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            @Override
5339a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            public void go() {
534bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                mActivity.getListHandler()
535bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                .onConversationSelected(null, true /* inLoaderCallbacks */);
536bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            }
537bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp
538bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        });
539bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp    }
540bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp
5414d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    protected void onConversationSeen() {
5424d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // Ignore unsafe calls made after a fragment is detached from an activity
5434d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        final ControllableActivity activity = (ControllableActivity) getActivity();
5444d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        if (activity == null) {
5454d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            LogUtils.w(LOG_TAG, "ignoring onConversationSeen for conv=%s", mConversation.id);
5464d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            return;
5474d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
5484d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
5494d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        mViewState.setInfoForConversation(mConversation);
5504d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
5514d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // mark viewed/read if not previously marked viewed by this conversation view,
5524d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // or if unread messages still exist in the message list cursor
5534d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // we don't want to keep marking viewed on rotation or restore
5544d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // but we do want future re-renders to mark read (e.g. "New message from X" case)
5554d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        MessageCursor cursor = getMessageCursor();
5564d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        if (!mConversation.isViewed() || (cursor != null && !cursor.isConversationRead())) {
5574d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            final ConversationUpdater listController = activity.getConversationUpdater();
5584d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            // The conversation cursor may not have finished loading by now (when launched via
5594d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            // notification), so watch for when it finishes and mark it read then.
5604d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            if (listController.getConversationListCursor() == null) {
5614d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                LogUtils.i(LOG_TAG, "deferring conv mark read on open for id=%d",
5624d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                        mConversation.id);
5634d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                mMarkReadObserver = new MarkReadObserver(listController);
5644d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                listController.registerConversationListObserver(mMarkReadObserver);
5654d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            } else {
5664d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                markReadOnSeen(listController);
5674d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            }
5684d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
5694d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
5704d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        activity.getListHandler().onConversationSeen(mConversation);
5714d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    }
5724d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
5734d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    protected void markReadOnSeen(ConversationUpdater listController) {
5744d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // Mark the conversation viewed and read.
5754d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        listController.markConversationsRead(Arrays.asList(mConversation), true /* read */,
5764d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                true /* viewed */);
5774d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
5784d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // and update the Message objects in the cursor so the next time a cursor update happens
5794d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // with these messages marked read, we know to ignore it
5804d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        MessageCursor cursor = getMessageCursor();
5814d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        if (cursor != null) {
5824d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            cursor.markMessagesRead();
5834d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
5844d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    }
5854d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
5864d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    protected ConversationViewState getNewViewState() {
5874d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        return new ConversationViewState();
5884d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    }
5894d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
590f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private static class MessageLoader extends CursorLoader {
591f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private boolean mDeliveredFirstResults = false;
592f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private final Conversation mConversation;
593f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private final ConversationController mController;
594f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
595f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public MessageLoader(Context c, Conversation conv, ConversationController controller) {
596f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            super(c, conv.messageListUri, UIProvider.MESSAGE_PROJECTION, null, null, null);
597f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mConversation = conv;
598f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mController = controller;
599f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
600f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
601f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
602f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public Cursor loadInBackground() {
603f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return new MessageCursor(super.loadInBackground(), mConversation, mController);
604f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
605f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
606f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
607f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void deliverResult(Cursor result) {
608f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // We want to deliver these results, and then we want to make sure
609f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // that any subsequent
610f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // queries do not hit the network
611f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            super.deliverResult(result);
612f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
613f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (!mDeliveredFirstResults) {
614f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                mDeliveredFirstResults = true;
615f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                Uri uri = getUri();
616f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
617f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // Create a ListParams that tells the provider to not hit the
618f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // network
619f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                final ListParams listParams = new ListParams(ListParams.NO_LIMIT,
620f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        false /* useNetwork */);
621f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
622f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // Build the new uri with this additional parameter
623f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                uri = uri
624f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        .buildUpon()
625f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        .appendQueryParameter(UIProvider.LIST_PARAMS_QUERY_PARAMETER,
626f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                                listParams.serialize()).build();
627f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                setUri(uri);
628f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            }
629f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
630f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
631f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
632f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
633f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Inner class to to asynchronously load contact data for all senders in the conversation,
634f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * and notify observers when the data is ready.
635f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     *
636f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
637f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected class ContactLoaderCallbacks implements ContactInfoSource,
638f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            LoaderManager.LoaderCallbacks<ImmutableMap<String, ContactInfo>> {
639f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
640f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private Set<String> mSenders;
641f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private ImmutableMap<String, ContactInfo> mContactInfoMap;
642f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private DataSetObservable mObservable = new DataSetObservable();
643f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
644f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void setSenders(Set<String> emailAddresses) {
645f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mSenders = emailAddresses;
646f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
647f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
648f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
649f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public Loader<ImmutableMap<String, ContactInfo>> onCreateLoader(int id, Bundle args) {
650f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return new SenderInfoLoader(mActivity.getActivityContext(), mSenders);
651f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
652f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
653f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
654f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void onLoadFinished(Loader<ImmutableMap<String, ContactInfo>> loader,
655f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                ImmutableMap<String, ContactInfo> data) {
656f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mContactInfoMap = data;
657f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mObservable.notifyChanged();
658f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
659f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
660f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
661f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void onLoaderReset(Loader<ImmutableMap<String, ContactInfo>> loader) {
662f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
663f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
664f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
665f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public ContactInfo getContactInfo(String email) {
666f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (mContactInfoMap == null) {
667f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                return null;
668f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            }
669f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return mContactInfoMap.get(email);
670f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
671f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
672f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
673f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void registerObserver(DataSetObserver observer) {
674f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mObservable.registerObserver(observer);
675f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
676f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
677f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
678f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void unregisterObserver(DataSetObserver observer) {
679f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mObservable.unregisterObserver(observer);
680f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
681542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook    }
682542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
683542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook    protected class AbstractConversationWebViewClient extends WebViewClient {
684542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook        @Override
685542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook        public boolean shouldOverrideUrlLoading(WebView view, String url) {
686542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            final Activity activity = getActivity();
687542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            if (activity == null) {
688542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                return false;
689542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            }
690542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
691542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            boolean result = false;
692542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            final Intent intent;
693542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            Uri uri = Uri.parse(url);
694542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            if (!Utils.isEmpty(mAccount.viewIntentProxyUri)) {
695542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                intent = generateProxyIntent(uri);
696542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            } else {
697542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                intent = new Intent(Intent.ACTION_VIEW, uri);
698542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                intent.putExtra(Browser.EXTRA_APPLICATION_ID, activity.getPackageName());
699542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            }
700f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
701542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            try {
702542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
703542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                activity.startActivity(intent);
704542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                result = true;
705542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            } catch (ActivityNotFoundException ex) {
706542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // If no application can handle the URL, assume that the
707542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // caller can handle it.
708542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            }
709542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
710542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            return result;
711542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook        }
712542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
713542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook        private Intent generateProxyIntent(Uri uri) {
714542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            final Intent intent = new Intent(Intent.ACTION_VIEW, mAccount.viewIntentProxyUri);
715542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            intent.putExtra(UIProvider.ViewProxyExtras.EXTRA_ORIGINAL_URI, uri);
716542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            intent.putExtra(UIProvider.ViewProxyExtras.EXTRA_ACCOUNT, mAccount);
717542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
718542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            final Context context = getContext();
719542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            PackageManager manager = null;
720542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            // We need to catch the exception to make CanvasConversationHeaderView
721542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            // test pass.  Bug: http://b/issue?id=3470653.
722542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            try {
723542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                manager = context.getPackageManager();
724542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            } catch (UnsupportedOperationException e) {
725542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                LogUtils.e(LOG_TAG, e, "Error getting package manager");
726542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            }
727542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
728542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            if (manager != null) {
729542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // Try and resolve the intent, to find an activity from this package
730542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                final List<ResolveInfo> resolvedActivities = manager.queryIntentActivities(
731542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                        intent, PackageManager.MATCH_DEFAULT_ONLY);
732542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
733542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                final String packageName = context.getPackageName();
734542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
735542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // Now try and find one that came from this package, if one is not found, the UI
736542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // provider must have specified an intent that is to be handled by a different apk.
737542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // In that case, the class name will not be set on the intent, so the default
738542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // intent resolution will be used.
739542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                for (ResolveInfo resolveInfo: resolvedActivities) {
740542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                    final ActivityInfo activityInfo = resolveInfo.activityInfo;
741542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                    if (packageName.equals(activityInfo.packageName)) {
742542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                        intent.setClassName(activityInfo.packageName, activityInfo.name);
743542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                        break;
744542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                    }
745542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                }
746542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            }
747542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
748542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            return intent;
749542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook        }
750f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
751542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
7524d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    private class MarkReadObserver extends DataSetObserver {
7534d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        private final ConversationUpdater mListController;
7544d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
7554d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        private MarkReadObserver(ConversationUpdater listController) {
7564d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            mListController = listController;
7574d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
7584d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
7594d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        @Override
7604d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        public void onChanged() {
7614d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            if (mListController.getConversationListCursor() == null) {
7624d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                // nothing yet, keep watching
7634d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                return;
7644d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            }
7654d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            // done loading, safe to mark read now
7664d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            mListController.unregisterConversationListObserver(this);
7674d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            mMarkReadObserver = null;
7684d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            LogUtils.i(LOG_TAG, "running deferred conv mark read on open, id=%d", mConversation.id);
7694d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            markReadOnSeen(mListController);
7704d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
7714d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    }
7724d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
77326d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp    public abstract void onConversationUpdated(Conversation conversation);
77426d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp
7759d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    /**
7769d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * Small Runnable-like wrapper that first checks that the Fragment is in a good state before
7779d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * doing any work. Ideal for use with a {@link Handler}.
7789d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     */
7799d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    protected abstract class FragmentRunnable implements Runnable {
7809d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
7819d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        private final String mOpName;
7829d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
7839d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        public FragmentRunnable(String opName) {
7849d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mOpName = opName;
7859d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
7869d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
7879d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        public abstract void go();
7889d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
7899d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        @Override
7909d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        public void run() {
7919d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            if (!isAdded()) {
7929d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                LogUtils.i(LOG_TAG, "Unable to run op='%s' b/c fragment is not attached: %s",
7939d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                        mOpName, AbstractConversationViewFragment.this);
7949d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                return;
7959d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            }
7969d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            go();
7979d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
7989d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
7999d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
8009d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
801f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp}
802