AbstractConversationViewFragment.java revision 233d435cba55eda0df580d3549b8a4e3b8d789a8
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
1267d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    private final Runnable mDelayedShow = new Runnable() {
127ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        @Override
128ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        public void run() {
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";
1459d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private static final String BUNDLE_USER_VISIBLE =
1469d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            AbstractConversationViewFragment.class.getName() + "uservisible";
1479d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
148f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public static Bundle makeBasicArgs(Account account, Folder folder) {
149f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        Bundle args = new Bundle();
150f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        args.putParcelable(ARG_ACCOUNT, account);
151f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        args.putParcelable(ARG_FOLDER, folder);
152f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return args;
153f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
154f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
155f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
156f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Constructor needs to be public to handle orientation changes and activity
157f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * lifecycle events.
158f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
159f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public AbstractConversationViewFragment() {
160f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super();
161f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
162f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
163f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
164f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override, since this depends on how many messages are
165f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * shown in the conversation view.
166f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
167f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected abstract void markUnread();
168f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
169f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
170f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override this, since they may want to display a single or
171f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * many messages related to this conversation.
172f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
173014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang    protected abstract void onMessageCursorLoadFinished(Loader<Cursor> loader,
174014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            MessageCursor newCursor, MessageCursor oldCursor);
175f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
176f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
177f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override this, since they may want to display a single or
178f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * many messages related to this conversation.
179f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
180f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
181f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public abstract void onConversationViewHeaderHeightChange(int newHeight);
182f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
183f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public abstract void onUserVisibleHintChanged();
184f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
185f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
186f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Subclasses must override this.
187f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
188adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    protected abstract void onAccountChanged(Account newAccount, Account oldAccount);
189f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
190f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
191f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onCreate(Bundle savedState) {
192f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onCreate(savedState);
193f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
194f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        final Bundle args = getArguments();
195f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mAccount = args.getParcelable(ARG_ACCOUNT);
196f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mConversation = args.getParcelable(ARG_CONVERSATION);
197f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mFolder = args.getParcelable(ARG_FOLDER);
198ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook
199ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook        // Since the uri specified in the conversation base uri may not be unique, we specify a
200ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook        // base uri that us guaranteed to be unique for this conversation.
201ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook        mBaseUri = "x-thread://" + mAccount.name + "/" + mConversation.id;
202ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook
203f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // On JB or newer, we use the 'webkitAnimationStart' DOM event to signal load complete
204f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // Below JB, try to speed up initial render by having the webview do supplemental draws to
205f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // custom a software canvas.
206f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // TODO(mindyp):
207f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        //PAGE READINESS SIGNAL FOR JELLYBEAN AND NEWER
208f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // Notify the app on 'webkitAnimationStart' of a simple dummy element with a simple no-op
209f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // animation that immediately runs on page load. The app uses this as a signal that the
210f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // content is loaded and ready to draw, since WebView delays firing this event until the
211f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // layers are composited and everything is ready to draw.
212f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // This signal does not seem to be reliable, so just use the old method for now.
213f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mEnableContentReadySignal = false; //Utils.isRunningJellybeanOrLater();
214f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        LogUtils.d(LOG_TAG, "onCreate in ConversationViewFragment (this=%s)", this);
215f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // Not really, we just want to get a crack to store a reference to the change_folder item
216f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        setHasOptionsMenu(true);
2179d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
2189d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (savedState != null) {
2199d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mViewState = savedState.getParcelable(BUNDLE_VIEW_STATE);
2209d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mUserVisible = savedState.getBoolean(BUNDLE_USER_VISIBLE);
2219d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        } else {
2229d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mViewState = getNewViewState();
2239d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
224f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
225f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
226ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    public void instantiateProgressIndicators(View rootView) {
227ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        mBackgroundView = rootView.findViewById(R.id.background_view);
228ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        mProgressView = rootView.findViewById(R.id.loading_progress);
229ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    }
230ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
231ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    protected void dismissLoadingStatus() {
2327d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang        dismissLoadingStatus(null);
2337d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    }
2347d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang
2357d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    /**
2367d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang     * Begin the fade-out animation to hide the Progress overlay, either immediately or after some
2377d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang     * timeout (to ensure that the progress minimum time elapses).
2387d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang     *
2397d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang     * @param doAfter an optional Runnable action to execute after the animation completes
2407d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang     */
2417d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    protected void dismissLoadingStatus(final Runnable doAfter) {
24232d911f491afad153983f7519267ddb764927355mindyp        if (mLoadingShownTime == -1) {
243ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp            // The runnable hasn't run yet, so just remove it.
244ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp            mHandler.removeCallbacks(mDelayedShow);
2457d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang            dismiss(doAfter);
246ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp            return;
247ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        }
24832d911f491afad153983f7519267ddb764927355mindyp        final long diff = Math.abs(System.currentTimeMillis() - mLoadingShownTime);
24932d911f491afad153983f7519267ddb764927355mindyp        if (diff > sMinShowTime) {
2507d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang            dismiss(doAfter);
25132d911f491afad153983f7519267ddb764927355mindyp        } else {
2527d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang            mHandler.postDelayed(new Runnable() {
2537d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                @Override
2547d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                public void run() {
2557d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                    dismiss(doAfter);
2567d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                }
2577d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang            }, Math.abs(sMinShowTime - diff));
25832d911f491afad153983f7519267ddb764927355mindyp        }
25932d911f491afad153983f7519267ddb764927355mindyp    }
26032d911f491afad153983f7519267ddb764927355mindyp
2617d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    private void dismiss(final Runnable doAfter) {
26232d911f491afad153983f7519267ddb764927355mindyp        // Reset loading shown time.
26332d911f491afad153983f7519267ddb764927355mindyp        mLoadingShownTime = -1;
264ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        // Fade out the info view.
265ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        if (mBackgroundView.getVisibility() == View.VISIBLE) {
2667d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang            mProgressView.setVisibility(View.GONE);
2677d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang            final Animator animator = AnimatorInflater.loadAnimator(getContext(), R.anim.fade_out);
268ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp            animator.setTarget(mBackgroundView);
2697d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang            animator.addListener(new AnimatorListenerAdapter() {
270ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp                @Override
271ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp                public void onAnimationEnd(Animator animation) {
272ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp                    mBackgroundView.setVisibility(View.GONE);
2737d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                    if (doAfter != null) {
2747d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                        doAfter.run();
2757d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                    }
276ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp                }
277ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp            });
278ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp            animator.start();
279ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        } else {
280ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp            mBackgroundView.setVisibility(View.GONE);
281ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp            mProgressView.setVisibility(View.GONE);
282ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        }
283ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    }
284ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
285f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
286f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onActivityCreated(Bundle savedInstanceState) {
287f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onActivityCreated(savedInstanceState);
2888fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal        final Activity activity = getActivity();
289f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (!(activity instanceof ControllableActivity)) {
290f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            LogUtils.wtf(LOG_TAG, "ConversationViewFragment expects only a ControllableActivity to"
291f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    + "create it. Cannot proceed.");
292f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
2938fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal        if (activity == null || activity.isFinishing()) {
294f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // Activity is finishing, just bail.
295f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return;
296f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
2978fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal        mActivity = (ControllableActivity) activity;
298f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mContext = activity.getApplicationContext();
299f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mDateBuilder = new FormattedDateBuilder((Context) mActivity);
300f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mAccount = mAccountObserver.initialize(mActivity.getAccountController());
301f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
302f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
303f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
304f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public ConversationUpdater getListController() {
305f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        final ControllableActivity activity = (ControllableActivity) getActivity();
306f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return activity != null ? activity.getConversationUpdater() : null;
307f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
308f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
309ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
310ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    protected void showLoadingStatus() {
31132d911f491afad153983f7519267ddb764927355mindyp        if (!mUserVisible) {
31232d911f491afad153983f7519267ddb764927355mindyp            return;
31332d911f491afad153983f7519267ddb764927355mindyp        }
314ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        if (sMinDelay == -1) {
31532d911f491afad153983f7519267ddb764927355mindyp            Resources res = getContext().getResources();
31632d911f491afad153983f7519267ddb764927355mindyp            sMinDelay = res.getInteger(R.integer.conversationview_show_loading_delay);
31732d911f491afad153983f7519267ddb764927355mindyp            sMinShowTime = res.getInteger(R.integer.conversationview_min_show_loading);
318ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        }
31932d911f491afad153983f7519267ddb764927355mindyp        // If the loading view isn't already showing, show it and remove any
32032d911f491afad153983f7519267ddb764927355mindyp        // pending calls to show the loading screen.
32132d911f491afad153983f7519267ddb764927355mindyp        mBackgroundView.setVisibility(View.VISIBLE);
322ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        mHandler.removeCallbacks(mDelayedShow);
323ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        mHandler.postDelayed(mDelayedShow, sMinDelay);
324ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    }
325ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
326f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public Context getContext() {
327f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mContext;
328f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
329f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
330f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public Conversation getConversation() {
331f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mConversation;
332f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
333f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
334f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
335f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public MessageCursor getMessageCursor() {
336f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mCursor;
337f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
338f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
339ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    public Handler getHandler() {
340ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        return mHandler;
341ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp    }
342ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp
343f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public MessageLoaderCallbacks getMessageLoaderCallbacks() {
344f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mMessageLoaderCallbacks;
345f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
346f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
347f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public ContactLoaderCallbacks getContactInfoSource() {
348f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mContactLoaderCallbacks;
349f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
350f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
351f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
352f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public Account getAccount() {
353f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return mAccount;
354f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
355f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
356f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
357f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
358f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onCreateOptionsMenu(menu, inflater);
359f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mChangeFoldersMenuItem = menu.findItem(R.id.change_folder);
360f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
361f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
362f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
363f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public boolean onOptionsItemSelected(MenuItem item) {
364f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        boolean handled = false;
365f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        switch (item.getItemId()) {
366f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            case R.id.inside_conversation_unread:
367f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                markUnread();
368f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                handled = true;
369f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                break;
370f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
371f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return handled;
372f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
373f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
374f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    // BEGIN conversation header callbacks
375f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
376f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onFoldersClicked() {
377f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (mChangeFoldersMenuItem == null) {
378f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            LogUtils.e(LOG_TAG, "unable to open 'change folders' dialog for a conversation");
379f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return;
380f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
381f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mActivity.onOptionsItemSelected(mChangeFoldersMenuItem);
382f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
383f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
384f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
385f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public String getSubjectRemainder(String subject) {
386f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        final SubjectDisplayChanger sdc = mActivity.getSubjectDisplayChanger();
387f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (sdc == null) {
388f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return subject;
389f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
390f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return sdc.getUnshownSubject(subject);
391f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
392f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    // END conversation header callbacks
393f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
394f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
3959d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    public void onSaveInstanceState(Bundle outState) {
3969d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (mViewState != null) {
3979d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            outState.putParcelable(BUNDLE_VIEW_STATE, mViewState);
3989d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
3999d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        outState.putBoolean(BUNDLE_USER_VISIBLE, mUserVisible);
4009d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
4019d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
4029d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    @Override
403f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onDestroyView() {
404f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        super.onDestroyView();
405f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mAccountObserver.unregisterAndDestroy();
4064d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        if (mMarkReadObserver != null) {
4074d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            mActivity.getConversationUpdater().unregisterConversationListObserver(
4084d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                    mMarkReadObserver);
4094d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            mMarkReadObserver = null;
4104d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
411f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
412f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
413f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
414f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * {@link #setUserVisibleHint(boolean)} only works on API >= 15, so implement our own for
415f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * reliability on older platforms.
416f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
417f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void setExtraUserVisibleHint(boolean isVisibleToUser) {
418f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        LogUtils.v(LOG_TAG, "in CVF.setHint, val=%s (%s)", isVisibleToUser, this);
419f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (mUserVisible != isVisibleToUser) {
420f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mUserVisible = isVisibleToUser;
421bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            MessageCursor cursor = getMessageCursor();
4220b9b48c1ac510d6d0d043a86354880ceb57a8175mindyp            if (mUserVisible && (cursor != null && cursor.isLoaded() && cursor.getCount() == 0)) {
423bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                // Pop back to conversation list and show error.
424bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                onError();
425bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                return;
426bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            }
427f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            onUserVisibleHintChanged();
428f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
429f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
430f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
4319d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    public boolean isUserVisible() {
4329d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        return mUserVisible;
4339d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
4349d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
435f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private class MessageLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
436f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
437f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
438f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
439f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return new MessageLoader(mActivity.getActivityContext(), mConversation,
440f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    AbstractConversationViewFragment.this);
441f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
442f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
443f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
444f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
445f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // ignore truly duplicate results
446f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // this can happen when restoring after rotation
447f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (mCursor == data) {
448f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                return;
449f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            } else {
4506766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang                final MessageCursor messageCursor = (MessageCursor) data;
451f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
452f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
453f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    LogUtils.d(LOG_TAG, "LOADED CONVERSATION= %s", messageCursor.getDebugDump());
454f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
455f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
456f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // When the last cursor had message(s), and the new version has
457f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // no messages, we need to exit conversation view.
458f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                if (messageCursor.getCount() == 0 && mCursor != null) {
459f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    if (mUserVisible) {
460bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                        onError();
461f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    } else {
462f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // we expect that the pager adapter will remove this
463f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // conversation fragment on its own due to a separate
464f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // conversation cursor update (we might get here if the
465f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // message list update fires first. nothing to do
466f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        // because we expect to be torn down soon.)
467f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        LogUtils.i(LOG_TAG, "CVF: offscreen conv has no messages, ignoring update"
468f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                                + " in anticipation of conv cursor update. c=%s", mConversation.uri);
469f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    }
470233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // existing mCursor will imminently be closed, must stop referencing it
471233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // since we expect to be kicked out soon, it doesn't matter what mCursor
472233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // becomes
473233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    mCursor = null;
474f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    return;
475f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
476f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
477f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // ignore cursors that are still loading results
478f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                if (!messageCursor.isLoaded()) {
479233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // existing mCursor will imminently be closed, must stop referencing it
480233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // in this case, the new cursor is also no good, and since don't expect to get
481233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // here except in initial load situations, it's safest to just ensure the
482233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    // reference is null
483233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang                    mCursor = null;
484f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    return;
485f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
486014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                final MessageCursor oldCursor = mCursor;
4876766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang                mCursor = messageCursor;
488014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                onMessageCursorLoadFinished(loader, mCursor, oldCursor);
489f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            }
490f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
491f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
492f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
493f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void onLoaderReset(Loader<Cursor> loader) {
494f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mCursor = null;
495f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
496f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
497f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
498f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
499bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp    private void onError() {
500bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // need to exit this view- conversation may have been
501bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // deleted, or for whatever reason is now invalid (e.g.
502bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // discard single draft)
503bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        //
504bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // N.B. this may involve a fragment transaction, which
505bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // FragmentManager will refuse to execute directly
506bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // within onLoadFinished. Make sure the controller knows.
507bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        LogUtils.i(LOG_TAG, "CVF: visible conv has no messages, exiting conv mode");
508bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // TODO(mindyp): handle ERROR status by showing an error
509bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // message to the user that there are no messages in
510bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        // this conversation
511bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        mHandler.post(new Runnable() {
512bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp
513bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            @Override
514bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            public void run() {
515bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                mActivity.getListHandler()
516bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp                .onConversationSelected(null, true /* inLoaderCallbacks */);
517bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp            }
518bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp
519bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp        });
520bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp    }
521bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp
5224d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    protected void onConversationSeen() {
5234d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // Ignore unsafe calls made after a fragment is detached from an activity
5244d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        final ControllableActivity activity = (ControllableActivity) getActivity();
5254d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        if (activity == null) {
5264d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            LogUtils.w(LOG_TAG, "ignoring onConversationSeen for conv=%s", mConversation.id);
5274d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            return;
5284d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
5294d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
5304d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        mViewState.setInfoForConversation(mConversation);
5314d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
5324d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // mark viewed/read if not previously marked viewed by this conversation view,
5334d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // or if unread messages still exist in the message list cursor
5344d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // we don't want to keep marking viewed on rotation or restore
5354d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // but we do want future re-renders to mark read (e.g. "New message from X" case)
5364d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        MessageCursor cursor = getMessageCursor();
5374d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        if (!mConversation.isViewed() || (cursor != null && !cursor.isConversationRead())) {
5384d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            final ConversationUpdater listController = activity.getConversationUpdater();
5394d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            // The conversation cursor may not have finished loading by now (when launched via
5404d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            // notification), so watch for when it finishes and mark it read then.
5414d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            if (listController.getConversationListCursor() == null) {
5424d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                LogUtils.i(LOG_TAG, "deferring conv mark read on open for id=%d",
5434d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                        mConversation.id);
5444d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                mMarkReadObserver = new MarkReadObserver(listController);
5454d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                listController.registerConversationListObserver(mMarkReadObserver);
5464d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            } else {
5474d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                markReadOnSeen(listController);
5484d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            }
5494d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
5504d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
5514d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        activity.getListHandler().onConversationSeen(mConversation);
5524d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    }
5534d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
5544d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    protected void markReadOnSeen(ConversationUpdater listController) {
5554d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // Mark the conversation viewed and read.
5564d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        listController.markConversationsRead(Arrays.asList(mConversation), true /* read */,
5574d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                true /* viewed */);
5584d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
5594d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // and update the Message objects in the cursor so the next time a cursor update happens
5604d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        // with these messages marked read, we know to ignore it
5614d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        MessageCursor cursor = getMessageCursor();
5624d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        if (cursor != null) {
5634d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            cursor.markMessagesRead();
5644d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
5654d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    }
5664d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
5674d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    protected ConversationViewState getNewViewState() {
5684d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        return new ConversationViewState();
5694d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    }
5704d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
571f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private static class MessageLoader extends CursorLoader {
572f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private boolean mDeliveredFirstResults = false;
573f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private final Conversation mConversation;
574f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private final ConversationController mController;
575f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
576f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public MessageLoader(Context c, Conversation conv, ConversationController controller) {
577f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            super(c, conv.messageListUri, UIProvider.MESSAGE_PROJECTION, null, null, null);
578f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mConversation = conv;
579f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mController = controller;
580f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
581f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
582f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
583f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public Cursor loadInBackground() {
584f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return new MessageCursor(super.loadInBackground(), mConversation, mController);
585f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
586f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
587f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
588f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void deliverResult(Cursor result) {
589f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // We want to deliver these results, and then we want to make sure
590f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // that any subsequent
591f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            // queries do not hit the network
592f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            super.deliverResult(result);
593f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
594f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (!mDeliveredFirstResults) {
595f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                mDeliveredFirstResults = true;
596f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                Uri uri = getUri();
597f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
598f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // Create a ListParams that tells the provider to not hit the
599f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // network
600f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                final ListParams listParams = new ListParams(ListParams.NO_LIMIT,
601f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        false /* useNetwork */);
602f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
603f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // Build the new uri with this additional parameter
604f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                uri = uri
605f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        .buildUpon()
606f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        .appendQueryParameter(UIProvider.LIST_PARAMS_QUERY_PARAMETER,
607f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                                listParams.serialize()).build();
608f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                setUri(uri);
609f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            }
610f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
611f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
612f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
613f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    /**
614f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * Inner class to to asynchronously load contact data for all senders in the conversation,
615f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     * and notify observers when the data is ready.
616f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     *
617f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp     */
618f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected class ContactLoaderCallbacks implements ContactInfoSource,
619f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            LoaderManager.LoaderCallbacks<ImmutableMap<String, ContactInfo>> {
620f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
621f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private Set<String> mSenders;
622f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private ImmutableMap<String, ContactInfo> mContactInfoMap;
623f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        private DataSetObservable mObservable = new DataSetObservable();
624f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
625f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void setSenders(Set<String> emailAddresses) {
626f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mSenders = emailAddresses;
627f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
628f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
629f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
630f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public Loader<ImmutableMap<String, ContactInfo>> onCreateLoader(int id, Bundle args) {
631f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return new SenderInfoLoader(mActivity.getActivityContext(), mSenders);
632f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
633f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
634f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
635f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void onLoadFinished(Loader<ImmutableMap<String, ContactInfo>> loader,
636f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                ImmutableMap<String, ContactInfo> data) {
637f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mContactInfoMap = data;
638f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mObservable.notifyChanged();
639f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
640f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
641f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
642f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void onLoaderReset(Loader<ImmutableMap<String, ContactInfo>> loader) {
643f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
644f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
645f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
646f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public ContactInfo getContactInfo(String email) {
647f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (mContactInfoMap == null) {
648f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                return null;
649f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            }
650f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            return mContactInfoMap.get(email);
651f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
652f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
653f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
654f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void registerObserver(DataSetObserver observer) {
655f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mObservable.registerObserver(observer);
656f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
657f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
658f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        @Override
659f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        public void unregisterObserver(DataSetObserver observer) {
660f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            mObservable.unregisterObserver(observer);
661f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        }
662542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook    }
663542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
664542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook    protected class AbstractConversationWebViewClient extends WebViewClient {
665542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook        @Override
666542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook        public boolean shouldOverrideUrlLoading(WebView view, String url) {
667542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            final Activity activity = getActivity();
668542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            if (activity == null) {
669542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                return false;
670542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            }
671542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
672542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            boolean result = false;
673542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            final Intent intent;
674542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            Uri uri = Uri.parse(url);
675542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            if (!Utils.isEmpty(mAccount.viewIntentProxyUri)) {
676542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                intent = generateProxyIntent(uri);
677542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            } else {
678542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                intent = new Intent(Intent.ACTION_VIEW, uri);
679542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                intent.putExtra(Browser.EXTRA_APPLICATION_ID, activity.getPackageName());
680542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            }
681f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
682542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            try {
683542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
684542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                activity.startActivity(intent);
685542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                result = true;
686542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            } catch (ActivityNotFoundException ex) {
687542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // If no application can handle the URL, assume that the
688542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // caller can handle it.
689542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            }
690542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
691542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            return result;
692542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook        }
693542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
694542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook        private Intent generateProxyIntent(Uri uri) {
695542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            final Intent intent = new Intent(Intent.ACTION_VIEW, mAccount.viewIntentProxyUri);
696542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            intent.putExtra(UIProvider.ViewProxyExtras.EXTRA_ORIGINAL_URI, uri);
697542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            intent.putExtra(UIProvider.ViewProxyExtras.EXTRA_ACCOUNT, mAccount);
698542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
699542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            final Context context = getContext();
700542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            PackageManager manager = null;
701542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            // We need to catch the exception to make CanvasConversationHeaderView
702542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            // test pass.  Bug: http://b/issue?id=3470653.
703542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            try {
704542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                manager = context.getPackageManager();
705542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            } catch (UnsupportedOperationException e) {
706542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                LogUtils.e(LOG_TAG, e, "Error getting package manager");
707542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            }
708542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
709542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            if (manager != null) {
710542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // Try and resolve the intent, to find an activity from this package
711542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                final List<ResolveInfo> resolvedActivities = manager.queryIntentActivities(
712542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                        intent, PackageManager.MATCH_DEFAULT_ONLY);
713542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
714542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                final String packageName = context.getPackageName();
715542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
716542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // Now try and find one that came from this package, if one is not found, the UI
717542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // provider must have specified an intent that is to be handled by a different apk.
718542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // In that case, the class name will not be set on the intent, so the default
719542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                // intent resolution will be used.
720542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                for (ResolveInfo resolveInfo: resolvedActivities) {
721542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                    final ActivityInfo activityInfo = resolveInfo.activityInfo;
722542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                    if (packageName.equals(activityInfo.packageName)) {
723542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                        intent.setClassName(activityInfo.packageName, activityInfo.name);
724542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                        break;
725542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                    }
726542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook                }
727542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            }
728542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
729542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            return intent;
730542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook        }
731f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    }
732542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook
7334d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    private class MarkReadObserver extends DataSetObserver {
7344d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        private final ConversationUpdater mListController;
7354d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
7364d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        private MarkReadObserver(ConversationUpdater listController) {
7374d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            mListController = listController;
7384d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
7394d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
7404d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        @Override
7414d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        public void onChanged() {
7424d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            if (mListController.getConversationListCursor() == null) {
7434d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                // nothing yet, keep watching
7444d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook                return;
7454d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            }
7464d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            // done loading, safe to mark read now
7474d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            mListController.unregisterConversationListObserver(this);
7484d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            mMarkReadObserver = null;
7494d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            LogUtils.i(LOG_TAG, "running deferred conv mark read on open, id=%d", mConversation.id);
7504d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            markReadOnSeen(mListController);
7514d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook        }
7524d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook    }
7534d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook
75426d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp    public abstract void onConversationUpdated(Conversation conversation);
75526d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp
7569d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    /**
7579d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * Small Runnable-like wrapper that first checks that the Fragment is in a good state before
7589d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * doing any work. Ideal for use with a {@link Handler}.
7599d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     */
7609d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    protected abstract class FragmentRunnable implements Runnable {
7619d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
7629d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        private final String mOpName;
7639d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
7649d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        public FragmentRunnable(String opName) {
7659d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mOpName = opName;
7669d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
7679d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
7689d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        public abstract void go();
7699d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
7709d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        @Override
7719d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        public void run() {
7729d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            if (!isAdded()) {
7739d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                LogUtils.i(LOG_TAG, "Unable to run op='%s' b/c fragment is not attached: %s",
7749d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                        mOpName, AbstractConversationViewFragment.this);
7759d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                return;
7769d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            }
7779d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            go();
7789d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
7799d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
7809d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
7819d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
782f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp}
783