AbstractConversationViewFragment.java revision 2b9d80eb16156173f11a02eae4f770d8c975927c
1f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp/* 2f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Copyright (C) 2012 Google Inc. 3f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Licensed to The Android Open Source Project. 4f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * 5f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Licensed under the Apache License, Version 2.0 (the "License"); 6f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * you may not use this file except in compliance with the License. 7f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * You may obtain a copy of the License at 8f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * 9f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * http://www.apache.org/licenses/LICENSE-2.0 10f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * 11f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Unless required by applicable law or agreed to in writing, software 12f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * distributed under the License is distributed on an "AS IS" BASIS, 13f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * See the License for the specific language governing permissions and 15f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * limitations under the License. 16f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp */ 17f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 18f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyppackage com.android.mail.ui; 19f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 20f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.app.Activity; 21f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.app.Fragment; 22f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.app.LoaderManager; 23f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.content.Context; 24f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.content.Loader; 25f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.database.Cursor; 26f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.net.Uri; 27f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.os.Bundle; 28ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindypimport android.os.Handler; 29f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.view.Menu; 30f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.view.MenuInflater; 31f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.view.MenuItem; 32f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 33f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.R; 348f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huangimport com.android.mail.browse.ConversationAccountController; 358812d3c50e35c4f2a02d29c35c76082c4ebec0cdAndrew Sappersteinimport com.android.mail.browse.ConversationMessage; 36f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.browse.ConversationViewHeader.ConversationViewHeaderCallbacks; 379d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huangimport com.android.mail.browse.MessageCursor; 38f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.browse.MessageCursor.ConversationController; 39c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrookimport com.android.mail.content.ObjectCursor; 40c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrookimport com.android.mail.content.ObjectCursorLoader; 41f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.Account; 42f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.AccountObserver; 43f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.Address; 44f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.Conversation; 45f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.ListParams; 46f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.UIProvider; 47a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwalimport com.android.mail.providers.UIProvider.CursorStatus; 48f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.LogTag; 49f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.LogUtils; 50f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.Utils; 51f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 524d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrookimport java.util.Arrays; 53543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huangimport java.util.Collections; 54543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huangimport java.util.HashMap; 55f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport java.util.Map; 56376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein 57f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 58f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyppublic abstract class AbstractConversationViewFragment extends Fragment implements 594ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein ConversationController, ConversationAccountController, 60f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp ConversationViewHeaderCallbacks { 61f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 62606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein protected static final String ARG_ACCOUNT = "account"; 63f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public static final String ARG_CONVERSATION = "conversation"; 64f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp private static final String LOG_TAG = LogTag.getLogTag(); 65f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp protected static final int MESSAGE_LOADER = 0; 66f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp protected static final int CONTACT_LOADER = 1; 67f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp protected ControllableActivity mActivity; 68f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp private final MessageLoaderCallbacks mMessageLoaderCallbacks = new MessageLoaderCallbacks(); 69376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein private ContactLoaderCallbacks mContactLoaderCallbacks; 70f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp private MenuItem mChangeFoldersMenuItem; 71f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp protected Conversation mConversation; 72f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp protected String mBaseUri; 73f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp protected Account mAccount; 74376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein 75376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein /** 76376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein * Must be instantiated in a derived class's onCreate. 77376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein */ 78376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein protected AbstractConversationWebViewClient mWebViewClient; 794ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein 80543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang /** 81543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang * Cache of email address strings to parsed Address objects. 82543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang * <p> 83543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang * Remember to synchronize on the map when reading or writing to this cache, because some 84543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang * instances use it off the UI thread (e.g. from WebView). 85543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang */ 86543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang protected final Map<String, Address> mAddressCache = Collections.synchronizedMap( 87543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang new HashMap<String, Address>()); 88f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp private MessageCursor mCursor; 89f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp private Context mContext; 909d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang /** 919d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * A backwards-compatible version of {{@link #getUserVisibleHint()}. Like the framework flag, 929d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * this flag is saved and restored. 939d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang */ 949d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private boolean mUserVisible; 95376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein 96ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp private final Handler mHandler = new Handler(); 97d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal /** True if we want to avoid marking the conversation as viewed and read. */ 98d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal private boolean mSuppressMarkingViewed; 994d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook /** 1004d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook * Parcelable state of the conversation view. Can safely be used without null checking any time 1019d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * after {@link #onCreate(Bundle)}. 1024d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook */ 1034d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook protected ConversationViewState mViewState; 1044d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook 10518176786f320b4d4312ad1682da057434f201c13Scott Kennedy private boolean mIsDetached; 10618176786f320b4d4312ad1682da057434f201c13Scott Kennedy 1072fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein private boolean mHasConversationBeenTransformed; 1082fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein private boolean mHasConversationTransformBeenReverted; 1092fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein 110f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp private final AccountObserver mAccountObserver = new AccountObserver() { 111f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 112f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public void onChanged(Account newAccount) { 113adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final Account oldAccount = mAccount; 114f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mAccount = newAccount; 115376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mWebViewClient.setAccount(mAccount); 116adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang onAccountChanged(newAccount, oldAccount); 117f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 118f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp }; 119f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 1209d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private static final String BUNDLE_VIEW_STATE = 1219d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang AbstractConversationViewFragment.class.getName() + "viewstate"; 1229a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang /** 1239a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang * We save the user visible flag so the various transitions that occur during rotation do not 1249a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang * cause unnecessary visibility change. 1259a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang */ 1269d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private static final String BUNDLE_USER_VISIBLE = 1279d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang AbstractConversationViewFragment.class.getName() + "uservisible"; 1289d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 12918176786f320b4d4312ad1682da057434f201c13Scott Kennedy private static final String BUNDLE_DETACHED = 13018176786f320b4d4312ad1682da057434f201c13Scott Kennedy AbstractConversationViewFragment.class.getName() + "detached"; 13118176786f320b4d4312ad1682da057434f201c13Scott Kennedy 1322fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein private static final String BUNDLE_KEY_HAS_CONVERSATION_BEEN_TRANSFORMED = 1332fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein AbstractConversationViewFragment.class.getName() + "conversationtransformed"; 1342fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein private static final String BUNDLE_KEY_HAS_CONVERSATION_BEEN_REVERTED = 1352fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein AbstractConversationViewFragment.class.getName() + "conversationreverted"; 1362fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein 137606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein public static Bundle makeBasicArgs(Account account) { 138f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp Bundle args = new Bundle(); 139f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp args.putParcelable(ARG_ACCOUNT, account); 140f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return args; 141f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 142f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 143f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp /** 144f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Constructor needs to be public to handle orientation changes and activity 145f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * lifecycle events. 146f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp */ 147f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public AbstractConversationViewFragment() { 148f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp super(); 149f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 150f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 151f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp /** 152f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Subclasses must override, since this depends on how many messages are 153f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * shown in the conversation view. 154f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp */ 155d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal protected void markUnread() { 156d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal // Do not automatically mark this conversation viewed and read. 157d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal mSuppressMarkingViewed = true; 158d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal } 159f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 160f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp /** 161f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Subclasses must override this, since they may want to display a single or 162f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * many messages related to this conversation. 163f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp */ 164c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook protected abstract void onMessageCursorLoadFinished( 165c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook Loader<ObjectCursor<ConversationMessage>> loader, 166014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang MessageCursor newCursor, MessageCursor oldCursor); 167f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 168f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp /** 169f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Subclasses must override this, since they may want to display a single or 170f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * many messages related to this conversation. 171f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp */ 172f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 173f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public abstract void onConversationViewHeaderHeightChange(int newHeight); 174f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 175f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public abstract void onUserVisibleHintChanged(); 176f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 177f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp /** 178f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Subclasses must override this. 179f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp */ 180adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang protected abstract void onAccountChanged(Account newAccount, Account oldAccount); 181f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 182f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 183f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public void onCreate(Bundle savedState) { 184f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp super.onCreate(savedState); 185f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 186606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein parseArguments(); 187606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein setBaseUri(); 188ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook 189f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.d(LOG_TAG, "onCreate in ConversationViewFragment (this=%s)", this); 190f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // Not really, we just want to get a crack to store a reference to the change_folder item 191f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp setHasOptionsMenu(true); 1929d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 1939d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (savedState != null) { 1949d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang mViewState = savedState.getParcelable(BUNDLE_VIEW_STATE); 1959d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang mUserVisible = savedState.getBoolean(BUNDLE_USER_VISIBLE); 19618176786f320b4d4312ad1682da057434f201c13Scott Kennedy mIsDetached = savedState.getBoolean(BUNDLE_DETACHED, false); 1972fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationBeenTransformed = 1982fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein savedState.getBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_TRANSFORMED, false); 1992fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationTransformBeenReverted = 2002fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein savedState.getBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_REVERTED, false); 2019d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } else { 2029d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang mViewState = getNewViewState(); 2032fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationBeenTransformed = false; 2042fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationTransformBeenReverted = false; 2059d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 206f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 207f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 208606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein /** 209606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein * Can be overridden in case a subclass needs to get additional arguments. 210606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein */ 211606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein protected void parseArguments() { 212606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein final Bundle args = getArguments(); 213606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein mAccount = args.getParcelable(ARG_ACCOUNT); 214606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein mConversation = args.getParcelable(ARG_CONVERSATION); 215606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein } 216606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein 217606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein /** 218606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein * Can be overridden in case a subclass needs a different uri format 219606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein * (such as one that does not rely on account and/or conversation. 220606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein */ 221606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein protected void setBaseUri() { 222606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein // Since the uri specified in the conversation base uri may not be unique, we specify a 223606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein // base uri that us guaranteed to be unique for this conversation. 224606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein mBaseUri = "x-thread://" + mAccount.name.hashCode() + "/" + mConversation.id; 225606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein } 226606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein 2279e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang @Override 2289e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang public String toString() { 2299e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang // log extra info at DEBUG level or finer 2309e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang final String s = super.toString(); 2319e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang if (!LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG) || mConversation == null) { 2329e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang return s; 2339e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang } 2349e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang return "(" + s + " conv=" + mConversation + ")"; 2359e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang } 2369e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang 237f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 238f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public void onActivityCreated(Bundle savedInstanceState) { 239f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp super.onActivityCreated(savedInstanceState); 2408fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal final Activity activity = getActivity(); 241f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (!(activity instanceof ControllableActivity)) { 242f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.wtf(LOG_TAG, "ConversationViewFragment expects only a ControllableActivity to" 243f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp + "create it. Cannot proceed."); 244f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 2458fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal if (activity == null || activity.isFinishing()) { 246f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // Activity is finishing, just bail. 247f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return; 248f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 2498fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal mActivity = (ControllableActivity) activity; 250f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mContext = activity.getApplicationContext(); 251b622d2b6750423c185a2a2463277e5e2b853fadaAndy Huang mWebViewClient.setActivity(activity); 252f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mAccount = mAccountObserver.initialize(mActivity.getAccountController()); 253376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mWebViewClient.setAccount(mAccount); 254f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 255f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 256f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 257f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public ConversationUpdater getListController() { 258f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp final ControllableActivity activity = (ControllableActivity) getActivity(); 259f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return activity != null ? activity.getConversationUpdater() : null; 260f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 261f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 262f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public Context getContext() { 263f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return mContext; 264f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 265f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 26602133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang @Override 267f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public Conversation getConversation() { 268f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return mConversation; 269f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 270f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 271f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 272f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public MessageCursor getMessageCursor() { 273f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return mCursor; 274f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 275f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 276ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp public Handler getHandler() { 277ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp return mHandler; 278ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp } 279ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp 280f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public MessageLoaderCallbacks getMessageLoaderCallbacks() { 281f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return mMessageLoaderCallbacks; 282f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 283f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 284f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public ContactLoaderCallbacks getContactInfoSource() { 285376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein if (mContactLoaderCallbacks == null) { 286376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mContactLoaderCallbacks = new ContactLoaderCallbacks(mActivity.getActivityContext()); 287376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein } 288f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return mContactLoaderCallbacks; 289f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 290f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 291f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 292f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public Account getAccount() { 293f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return mAccount; 294f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 295f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 296f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 297f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 298f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp super.onCreateOptionsMenu(menu, inflater); 299f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mChangeFoldersMenuItem = menu.findItem(R.id.change_folder); 300f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 301f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 302f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 303f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public boolean onOptionsItemSelected(MenuItem item) { 304bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang if (!isUserVisible()) { 305bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang // Unclear how this is happening. Current theory is that this fragment was scheduled 306bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang // to be removed, but the remove transaction failed. When the Activity is later 307bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang // restored, the FragmentManager restores this fragment, but Fragment.mMenuVisible is 308bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang // stuck at its initial value (true), which makes this zombie fragment eligible for 309bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang // menu item clicks. 310bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang // 311bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang // Work around this by relying on the (properly restored) extra user visible hint. 312bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang LogUtils.e(LOG_TAG, 313bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang "ACVF ignoring onOptionsItemSelected b/c userVisibleHint is false. f=%s", this); 314bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) { 315b184bfe96fa3512af88260fce4f3cee3066fb28dScott Kennedy LogUtils.e(LOG_TAG, Utils.dumpFragment(this)); // the dump has '%' chars in it... 316bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang } 317bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang return false; 318bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang } 319bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang 320f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp boolean handled = false; 3212b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy final int itemId = item.getItemId(); 3222b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy if (itemId == R.id.inside_conversation_unread) { 3232b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy markUnread(); 3242b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy handled = true; 3252b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy } else if (itemId == R.id.show_original) { 3262b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy showUntransformedConversation(); 3272b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy handled = true; 328f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 329f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return handled; 330f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 331f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 3322fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein @Override 3332fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein public void onPrepareOptionsMenu(Menu menu) { 3342fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein // Only show option if we support message transforms and message has been transformed. 3352fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein Utils.setMenuItemVisibility(menu, R.id.show_original, supportsMessageTransforms() && 3362fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationBeenTransformed && !mHasConversationTransformBeenReverted); 3372fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein } 3382fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein 3394ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein abstract boolean supportsMessageTransforms(); 3404ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein 341f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // BEGIN conversation header callbacks 342f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 343f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public void onFoldersClicked() { 344f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (mChangeFoldersMenuItem == null) { 345f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.e(LOG_TAG, "unable to open 'change folders' dialog for a conversation"); 346f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return; 347f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 348f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mActivity.onOptionsItemSelected(mChangeFoldersMenuItem); 349f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 350f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // END conversation header callbacks 351f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 352f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 3539d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang public void onSaveInstanceState(Bundle outState) { 3549d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (mViewState != null) { 3559d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang outState.putParcelable(BUNDLE_VIEW_STATE, mViewState); 3569d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 3579d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang outState.putBoolean(BUNDLE_USER_VISIBLE, mUserVisible); 35818176786f320b4d4312ad1682da057434f201c13Scott Kennedy outState.putBoolean(BUNDLE_DETACHED, mIsDetached); 3592fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein outState.putBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_TRANSFORMED, 3602fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationBeenTransformed); 3612fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein outState.putBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_REVERTED, 3622fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationTransformBeenReverted); 3639d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 3649d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 3659d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang @Override 366f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public void onDestroyView() { 367f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp super.onDestroyView(); 368f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mAccountObserver.unregisterAndDestroy(); 369f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 370f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 371f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp /** 372f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * {@link #setUserVisibleHint(boolean)} only works on API >= 15, so implement our own for 373f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * reliability on older platforms. 374f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp */ 375f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public void setExtraUserVisibleHint(boolean isVisibleToUser) { 376f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.v(LOG_TAG, "in CVF.setHint, val=%s (%s)", isVisibleToUser, this); 377f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (mUserVisible != isVisibleToUser) { 378f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mUserVisible = isVisibleToUser; 379bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp MessageCursor cursor = getMessageCursor(); 3800b9b48c1ac510d6d0d043a86354880ceb57a8175mindyp if (mUserVisible && (cursor != null && cursor.isLoaded() && cursor.getCount() == 0)) { 381bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // Pop back to conversation list and show error. 382bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp onError(); 383bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp return; 384bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp } 385f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp onUserVisibleHintChanged(); 386f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 387f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 388f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 3899d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang public boolean isUserVisible() { 3909d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang return mUserVisible; 3919d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 3929d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 393243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang protected void timerMark(String msg) { 394243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang if (isUserVisible()) { 395243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang Utils.sConvLoadTimer.mark(msg); 396243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang } 397243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang } 398243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang 399c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook private class MessageLoaderCallbacks 400c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook implements LoaderManager.LoaderCallbacks<ObjectCursor<ConversationMessage>> { 401f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 402f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 403c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook public Loader<ObjectCursor<ConversationMessage>> onCreateLoader(int id, Bundle args) { 40402133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang return new MessageLoader(mActivity.getActivityContext(), mConversation.messageListUri); 405f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 406f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 407f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 408c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook public void onLoadFinished(Loader<ObjectCursor<ConversationMessage>> loader, 409c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook ObjectCursor<ConversationMessage> data) { 410f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // ignore truly duplicate results 411f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // this can happen when restoring after rotation 412f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (mCursor == data) { 413f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return; 414f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } else { 4156766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang final MessageCursor messageCursor = (MessageCursor) data; 416f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 41702133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang // bind the cursor to this fragment so it can access to the current list controller 41802133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang messageCursor.setController(AbstractConversationViewFragment.this); 41902133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang 420f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) { 421f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.d(LOG_TAG, "LOADED CONVERSATION= %s", messageCursor.getDebugDump()); 422f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 423f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 424a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal // We have no messages: exit conversation view. 425a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal if (messageCursor.getCount() == 0 42618176786f320b4d4312ad1682da057434f201c13Scott Kennedy && (!CursorStatus.isWaitingForResults(messageCursor.getStatus()) 42718176786f320b4d4312ad1682da057434f201c13Scott Kennedy || mIsDetached)) { 428f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (mUserVisible) { 429bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp onError(); 430f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } else { 431f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // we expect that the pager adapter will remove this 432f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // conversation fragment on its own due to a separate 433f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // conversation cursor update (we might get here if the 434f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // message list update fires first. nothing to do 435f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // because we expect to be torn down soon.) 436f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.i(LOG_TAG, "CVF: offscreen conv has no messages, ignoring update" 437c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook + " in anticipation of conv cursor update. c=%s", 438c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook mConversation.uri); 439f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 440233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang // existing mCursor will imminently be closed, must stop referencing it 441233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang // since we expect to be kicked out soon, it doesn't matter what mCursor 442233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang // becomes 443233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang mCursor = null; 444f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return; 445f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 446f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 447f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // ignore cursors that are still loading results 448f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (!messageCursor.isLoaded()) { 449233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang // existing mCursor will imminently be closed, must stop referencing it 450233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang // in this case, the new cursor is also no good, and since don't expect to get 451233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang // here except in initial load situations, it's safest to just ensure the 452233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang // reference is null 453233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang mCursor = null; 454f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return; 455f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 456014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang final MessageCursor oldCursor = mCursor; 4576766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang mCursor = messageCursor; 458014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang onMessageCursorLoadFinished(loader, mCursor, oldCursor); 459f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 460f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 461f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 462f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 463c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook public void onLoaderReset(Loader<ObjectCursor<ConversationMessage>> loader) { 464f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mCursor = null; 465f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 466f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 467f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 468f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 469bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp private void onError() { 470bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // need to exit this view- conversation may have been 471bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // deleted, or for whatever reason is now invalid (e.g. 472bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // discard single draft) 473bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // 474bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // N.B. this may involve a fragment transaction, which 475bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // FragmentManager will refuse to execute directly 476bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // within onLoadFinished. Make sure the controller knows. 477bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp LogUtils.i(LOG_TAG, "CVF: visible conv has no messages, exiting conv mode"); 478bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // TODO(mindyp): handle ERROR status by showing an error 479bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // message to the user that there are no messages in 480bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // this conversation 48118176786f320b4d4312ad1682da057434f201c13Scott Kennedy popOut(); 48218176786f320b4d4312ad1682da057434f201c13Scott Kennedy } 483bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp 48418176786f320b4d4312ad1682da057434f201c13Scott Kennedy private void popOut() { 485376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mHandler.post(new FragmentRunnable("popOut", this) { 486bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp @Override 4879a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang public void go() { 4887729a9aaa1873505191303a35c480b6dcf1c7382Alice Yang if (mActivity != null) { 4897729a9aaa1873505191303a35c480b6dcf1c7382Alice Yang mActivity.getListHandler() 4907729a9aaa1873505191303a35c480b6dcf1c7382Alice Yang .onConversationSelected(null, true /* inLoaderCallbacks */); 4917729a9aaa1873505191303a35c480b6dcf1c7382Alice Yang } 492bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp } 493bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp }); 494bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp } 495bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp 4964d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook protected void onConversationSeen() { 497919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy LogUtils.d(LOG_TAG, "AbstractConversationViewFragment#onConversationSeen()"); 498919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy 4994d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook // Ignore unsafe calls made after a fragment is detached from an activity 5004d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook final ControllableActivity activity = (ControllableActivity) getActivity(); 5014d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook if (activity == null) { 5024d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook LogUtils.w(LOG_TAG, "ignoring onConversationSeen for conv=%s", mConversation.id); 5034d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook return; 5044d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook } 5054d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook 5064d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook mViewState.setInfoForConversation(mConversation); 5074d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook 508919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy LogUtils.d(LOG_TAG, "onConversationSeen() - mSuppressMarkingViewed = %b", 509919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy mSuppressMarkingViewed); 510d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal // In most circumstances we want to mark the conversation as viewed and read, since the 511d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal // user has read it. However, if the user has already marked the conversation unread, we 512d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal // do not want a later mark-read operation to undo this. So we check this variable which 513d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal // is set in #markUnread() which suppresses automatic mark-read. 514d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal if (!mSuppressMarkingViewed) { 515d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal // mark viewed/read if not previously marked viewed by this conversation view, 516d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal // or if unread messages still exist in the message list cursor 517d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal // we don't want to keep marking viewed on rotation or restore 518d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal // but we do want future re-renders to mark read (e.g. "New message from X" case) 519214522998157088ec7e38e0d2b4497e43f310ab7Paul Westbrook final MessageCursor cursor = getMessageCursor(); 520919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy LogUtils.d(LOG_TAG, "onConversationSeen() - mConversation.isViewed() = %b, " 521919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy + "cursor null = %b, cursor.isConversationRead() = %b", 522919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy mConversation.isViewed(), cursor == null, 523919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy cursor != null && cursor.isConversationRead()); 524d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal if (!mConversation.isViewed() || (cursor != null && !cursor.isConversationRead())) { 525d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal // Mark the conversation viewed and read. 526d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal activity.getConversationUpdater() 527d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal .markConversationsRead(Arrays.asList(mConversation), true, true); 528d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal 529d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal // and update the Message objects in the cursor so the next time a cursor update 530d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal // happens with these messages marked read, we know to ignore it 531214522998157088ec7e38e0d2b4497e43f310ab7Paul Westbrook if (cursor != null && !cursor.isClosed()) { 532d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal cursor.markMessagesRead(); 533d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal } 5344d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook } 5354d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook } 5363b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy activity.getListHandler().onConversationSeen(); 5374d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook } 5384d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook 5394d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook protected ConversationViewState getNewViewState() { 5404d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook return new ConversationViewState(); 5414d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook } 5424d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook 543c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook private static class MessageLoader extends ObjectCursorLoader<ConversationMessage> { 544f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp private boolean mDeliveredFirstResults = false; 545f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 54602133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang public MessageLoader(Context c, Uri messageListUri) { 547c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook super(c, messageListUri, UIProvider.MESSAGE_PROJECTION, ConversationMessage.FACTORY); 548f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 549f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 550f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 551c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook public void deliverResult(ObjectCursor<ConversationMessage> result) { 552f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // We want to deliver these results, and then we want to make sure 553f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // that any subsequent 554f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // queries do not hit the network 555f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp super.deliverResult(result); 556f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 557f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (!mDeliveredFirstResults) { 558f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mDeliveredFirstResults = true; 559f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp Uri uri = getUri(); 560f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 561f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // Create a ListParams that tells the provider to not hit the 562f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // network 563f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp final ListParams listParams = new ListParams(ListParams.NO_LIMIT, 564f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp false /* useNetwork */); 565f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 566f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // Build the new uri with this additional parameter 567f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp uri = uri 568f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp .buildUpon() 569f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp .appendQueryParameter(UIProvider.LIST_PARAMS_QUERY_PARAMETER, 570f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp listParams.serialize()).build(); 571f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp setUri(uri); 572f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 573f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 574c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook 575c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook @Override 576c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook protected ObjectCursor<ConversationMessage> getObjectCursor(Cursor inner) { 577c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook return new MessageCursor(inner); 578c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook } 579f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 580f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 58126d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp public abstract void onConversationUpdated(Conversation conversation); 58226d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp 58318176786f320b4d4312ad1682da057434f201c13Scott Kennedy public void onDetachedModeEntered() { 58418176786f320b4d4312ad1682da057434f201c13Scott Kennedy // If we have no messages, then we have nothing to display, so leave this view. 58518176786f320b4d4312ad1682da057434f201c13Scott Kennedy // Otherwise, just set the detached flag. 58618176786f320b4d4312ad1682da057434f201c13Scott Kennedy final Cursor messageCursor = getMessageCursor(); 58718176786f320b4d4312ad1682da057434f201c13Scott Kennedy 58818176786f320b4d4312ad1682da057434f201c13Scott Kennedy if (messageCursor == null || messageCursor.getCount() == 0) { 58918176786f320b4d4312ad1682da057434f201c13Scott Kennedy popOut(); 59018176786f320b4d4312ad1682da057434f201c13Scott Kennedy } else { 59118176786f320b4d4312ad1682da057434f201c13Scott Kennedy mIsDetached = true; 59218176786f320b4d4312ad1682da057434f201c13Scott Kennedy } 59318176786f320b4d4312ad1682da057434f201c13Scott Kennedy } 5942fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein 5952fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein /** 5962fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * Called when the JavaScript reports that it transformed a message. 5972fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * Sets a flag to true and invalidates the options menu so it will 5982fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * include the "Revert auto-sizing" menu option. 5992fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein */ 6002fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein public void onConversationTransformed() { 6012fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationBeenTransformed = true; 602376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mHandler.post(new FragmentRunnable("invalidateOptionsMenu", this) { 603ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein @Override 604ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein public void go() { 605ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein mActivity.invalidateOptionsMenu(); 606ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein } 607ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein }); 6082fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein } 6092fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein 6102fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein /** 6112fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * Called when the "Revert auto-sizing" option is selected. Default 6122fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * implementation simply sets a value on whether transforms should be 6132fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * applied. Derived classes should override this class and force a 6142fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * re-render so that the conversation renders without 6152fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein */ 6162fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein public void showUntransformedConversation() { 6172fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein // must set the value to true so we don't show the options menu item again 6182fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationTransformBeenReverted = true; 6192fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein } 6202fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein 6212fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein /** 6222fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * Returns {@code true} if the conversation should be transformed. {@code false}, otherwise. 6232fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * @return {@code true} if the conversation should be transformed. {@code false}, otherwise. 6242fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein */ 6252fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein public boolean shouldApplyTransforms() { 6263617b41394337774fb2e6fcf9fef1ac9eca00482Alice Yang return (mAccount.enableMessageTransforms > 0) && 6273617b41394337774fb2e6fcf9fef1ac9eca00482Alice Yang !mHasConversationTransformBeenReverted; 6282fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein } 629f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp} 630