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