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; 293f6111cf87080fe7adba512c7b9dfad2e4cd8154James Lemieuximport android.support.annotation.Nullable; 30f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.view.Menu; 31f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.view.MenuInflater; 32f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport android.view.MenuItem; 33f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 34821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerimport com.android.emailcommon.mail.Address; 35f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.R; 36761522c1ac7a8fa05acde714f0e26fde47f3415fAndy Huangimport com.android.mail.analytics.Analytics; 378f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huangimport com.android.mail.browse.ConversationAccountController; 388812d3c50e35c4f2a02d29c35c76082c4ebec0cdAndrew Sappersteinimport com.android.mail.browse.ConversationMessage; 39f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.browse.ConversationViewHeader.ConversationViewHeaderCallbacks; 409d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huangimport com.android.mail.browse.MessageCursor; 41f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.browse.MessageCursor.ConversationController; 42c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrookimport com.android.mail.content.ObjectCursor; 43c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrookimport com.android.mail.content.ObjectCursorLoader; 44f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.Account; 45f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.AccountObserver; 46f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.Conversation; 47e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huangimport com.android.mail.providers.Folder; 48f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.ListParams; 498ec43e877a9c1925514f066655984e21fbd255e8Andrew Sappersteinimport com.android.mail.providers.Settings; 50f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.providers.UIProvider; 51a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwalimport com.android.mail.providers.UIProvider.CursorStatus; 52f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.LogTag; 53f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.LogUtils; 54f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport com.android.mail.utils.Utils; 55f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 564d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrookimport java.util.Arrays; 57543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huangimport java.util.Collections; 58543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huangimport java.util.HashMap; 59f4fce1227d8b49f45e6569f1590565f2df9e8d6emindypimport java.util.Map; 60376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein 61f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyppublic abstract class AbstractConversationViewFragment extends Fragment implements 624ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein ConversationController, ConversationAccountController, 63f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp ConversationViewHeaderCallbacks { 64f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 65606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein protected static final String ARG_ACCOUNT = "account"; 66f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public static final String ARG_CONVERSATION = "conversation"; 67f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp private static final String LOG_TAG = LogTag.getLogTag(); 68f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp protected static final int MESSAGE_LOADER = 0; 69f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp protected static final int CONTACT_LOADER = 1; 704f347e811052f446c3958c76db278bcd7b39a44fAndy Huang public static final int ATTACHMENT_OPTION1_LOADER = 2; 71f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp protected ControllableActivity mActivity; 72f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp private final MessageLoaderCallbacks mMessageLoaderCallbacks = new MessageLoaderCallbacks(); 73376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein private ContactLoaderCallbacks mContactLoaderCallbacks; 74f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp private MenuItem mChangeFoldersMenuItem; 75f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp protected Conversation mConversation; 76f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp protected String mBaseUri; 77f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp protected Account mAccount; 78376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein 79376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein /** 80376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein * Must be instantiated in a derived class's onCreate. 81376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein */ 82376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein protected AbstractConversationWebViewClient mWebViewClient; 834ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein 84543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang /** 85543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang * Cache of email address strings to parsed Address objects. 86543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang * <p> 87543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang * Remember to synchronize on the map when reading or writing to this cache, because some 88543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang * instances use it off the UI thread (e.g. from WebView). 89543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang */ 90543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang protected final Map<String, Address> mAddressCache = Collections.synchronizedMap( 91543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang new HashMap<String, Address>()); 92f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp private MessageCursor mCursor; 93f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp private Context mContext; 949d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang /** 959d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * A backwards-compatible version of {{@link #getUserVisibleHint()}. Like the framework flag, 969d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * this flag is saved and restored. 979d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang */ 989d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private boolean mUserVisible; 99376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein 100ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp private final Handler mHandler = new Handler(); 101d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal /** True if we want to avoid marking the conversation as viewed and read. */ 102d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal private boolean mSuppressMarkingViewed; 1034d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook /** 1044d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook * Parcelable state of the conversation view. Can safely be used without null checking any time 1059d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * after {@link #onCreate(Bundle)}. 1064d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook */ 1074d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook protected ConversationViewState mViewState; 1084d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook 10918176786f320b4d4312ad1682da057434f201c13Scott Kennedy private boolean mIsDetached; 11018176786f320b4d4312ad1682da057434f201c13Scott Kennedy 1112fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein private boolean mHasConversationBeenTransformed; 1122fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein private boolean mHasConversationTransformBeenReverted; 1132fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein 114e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang protected boolean mConversationSeen = false; 115e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang 116f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp private final AccountObserver mAccountObserver = new AccountObserver() { 117f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 118f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public void onChanged(Account newAccount) { 119adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final Account oldAccount = mAccount; 120f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mAccount = newAccount; 121376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mWebViewClient.setAccount(mAccount); 122adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang onAccountChanged(newAccount, oldAccount); 123f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 124f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp }; 125f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 1269d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private static final String BUNDLE_VIEW_STATE = 1279d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang AbstractConversationViewFragment.class.getName() + "viewstate"; 1289a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang /** 1299a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang * We save the user visible flag so the various transitions that occur during rotation do not 1309a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang * cause unnecessary visibility change. 1319a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang */ 1329d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private static final String BUNDLE_USER_VISIBLE = 1339d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang AbstractConversationViewFragment.class.getName() + "uservisible"; 1349d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 13518176786f320b4d4312ad1682da057434f201c13Scott Kennedy private static final String BUNDLE_DETACHED = 13618176786f320b4d4312ad1682da057434f201c13Scott Kennedy AbstractConversationViewFragment.class.getName() + "detached"; 13718176786f320b4d4312ad1682da057434f201c13Scott Kennedy 1382fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein private static final String BUNDLE_KEY_HAS_CONVERSATION_BEEN_TRANSFORMED = 1392fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein AbstractConversationViewFragment.class.getName() + "conversationtransformed"; 1402fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein private static final String BUNDLE_KEY_HAS_CONVERSATION_BEEN_REVERTED = 1412fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein AbstractConversationViewFragment.class.getName() + "conversationreverted"; 1422fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein 143606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein public static Bundle makeBasicArgs(Account account) { 144f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp Bundle args = new Bundle(); 145f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp args.putParcelable(ARG_ACCOUNT, account); 146f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return args; 147f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 148f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 149f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp /** 150f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Constructor needs to be public to handle orientation changes and activity 151f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * lifecycle events. 152f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp */ 153f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public AbstractConversationViewFragment() { 154f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp super(); 155f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 156f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 157f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp /** 158f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Subclasses must override, since this depends on how many messages are 159f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * shown in the conversation view. 160f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp */ 161d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal protected void markUnread() { 162d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal // Do not automatically mark this conversation viewed and read. 163d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal mSuppressMarkingViewed = true; 164d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal } 165f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 166f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp /** 16787a8982af83168453b6dc219fe984bff68047bebAndy Huang * Marks a conversation either 'seen' (force=false), as in when the conversation is made visible 16887a8982af83168453b6dc219fe984bff68047bebAndy Huang * and should be marked read, or 'read' (force=true), as in when the action bar menu item to 16987a8982af83168453b6dc219fe984bff68047bebAndy Huang * mark this conversation read is selected. 17087a8982af83168453b6dc219fe984bff68047bebAndy Huang * 17187a8982af83168453b6dc219fe984bff68047bebAndy Huang * @param force true to force marking it read, false to allow peek mode to prevent it 17287a8982af83168453b6dc219fe984bff68047bebAndy Huang */ 17387a8982af83168453b6dc219fe984bff68047bebAndy Huang private final void markRead(boolean force) { 17487a8982af83168453b6dc219fe984bff68047bebAndy Huang final ControllableActivity activity = (ControllableActivity) getActivity(); 17587a8982af83168453b6dc219fe984bff68047bebAndy Huang if (activity == null) { 17687a8982af83168453b6dc219fe984bff68047bebAndy Huang return; 17787a8982af83168453b6dc219fe984bff68047bebAndy Huang } 17887a8982af83168453b6dc219fe984bff68047bebAndy Huang 17987a8982af83168453b6dc219fe984bff68047bebAndy Huang // mark viewed/read if not previously marked viewed by this conversation view, 18087a8982af83168453b6dc219fe984bff68047bebAndy Huang // or if unread messages still exist in the message list cursor 18187a8982af83168453b6dc219fe984bff68047bebAndy Huang // we don't want to keep marking viewed on rotation or restore 18287a8982af83168453b6dc219fe984bff68047bebAndy Huang // but we do want future re-renders to mark read (e.g. "New message from X" case) 18387a8982af83168453b6dc219fe984bff68047bebAndy Huang final MessageCursor cursor = getMessageCursor(); 18487a8982af83168453b6dc219fe984bff68047bebAndy Huang LogUtils.d(LOG_TAG, "onConversationSeen() - mConversation.isViewed() = %b, " 18587a8982af83168453b6dc219fe984bff68047bebAndy Huang + "cursor null = %b, cursor.isConversationRead() = %b", 18687a8982af83168453b6dc219fe984bff68047bebAndy Huang mConversation.isViewed(), cursor == null, 18787a8982af83168453b6dc219fe984bff68047bebAndy Huang cursor != null && cursor.isConversationRead()); 18887a8982af83168453b6dc219fe984bff68047bebAndy Huang if (!mConversation.isViewed() || (cursor != null && !cursor.isConversationRead())) { 18987a8982af83168453b6dc219fe984bff68047bebAndy Huang // Mark the conversation read no matter what if force=true. 19087a8982af83168453b6dc219fe984bff68047bebAndy Huang // else only mark it seen if appropriate (2-pane peek=true doesn't mark things seen) 19187a8982af83168453b6dc219fe984bff68047bebAndy Huang final boolean convMarkedRead; 19287a8982af83168453b6dc219fe984bff68047bebAndy Huang if (force) { 19387a8982af83168453b6dc219fe984bff68047bebAndy Huang activity.getConversationUpdater() 19487a8982af83168453b6dc219fe984bff68047bebAndy Huang .markConversationsRead(Arrays.asList(mConversation), true /* read */, 19587a8982af83168453b6dc219fe984bff68047bebAndy Huang true /* viewed */); 19687a8982af83168453b6dc219fe984bff68047bebAndy Huang convMarkedRead = true; 19787a8982af83168453b6dc219fe984bff68047bebAndy Huang } else { 19887a8982af83168453b6dc219fe984bff68047bebAndy Huang convMarkedRead = activity.getConversationUpdater() 19987a8982af83168453b6dc219fe984bff68047bebAndy Huang .markConversationSeen(mConversation); 20087a8982af83168453b6dc219fe984bff68047bebAndy Huang } 20187a8982af83168453b6dc219fe984bff68047bebAndy Huang 20287a8982af83168453b6dc219fe984bff68047bebAndy Huang // and update the Message objects in the cursor so the next time a cursor update 20387a8982af83168453b6dc219fe984bff68047bebAndy Huang // happens with these messages marked read, we know to ignore it 20487a8982af83168453b6dc219fe984bff68047bebAndy Huang if (convMarkedRead && cursor != null && !cursor.isClosed()) { 20587a8982af83168453b6dc219fe984bff68047bebAndy Huang cursor.markMessagesRead(); 20687a8982af83168453b6dc219fe984bff68047bebAndy Huang } 20787a8982af83168453b6dc219fe984bff68047bebAndy Huang } 20887a8982af83168453b6dc219fe984bff68047bebAndy Huang } 20987a8982af83168453b6dc219fe984bff68047bebAndy Huang 21087a8982af83168453b6dc219fe984bff68047bebAndy Huang /** 211f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Subclasses must override this, since they may want to display a single or 212f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * many messages related to this conversation. 213f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp */ 214c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook protected abstract void onMessageCursorLoadFinished( 215c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook Loader<ObjectCursor<ConversationMessage>> loader, 216014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang MessageCursor newCursor, MessageCursor oldCursor); 217f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 218f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp /** 219f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Subclasses must override this, since they may want to display a single or 220f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * many messages related to this conversation. 221f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp */ 222f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 223f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public abstract void onConversationViewHeaderHeightChange(int newHeight); 224f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 225f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public abstract void onUserVisibleHintChanged(); 226f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 227f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp /** 228f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * Subclasses must override this. 229f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp */ 230adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang protected abstract void onAccountChanged(Account newAccount, Account oldAccount); 231f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 232f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 233f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public void onCreate(Bundle savedState) { 234f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp super.onCreate(savedState); 235f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 236606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein parseArguments(); 237606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein setBaseUri(); 238ba4cce62e2de2b818190b81bb07ecc5e94544165Paul Westbrook 239f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.d(LOG_TAG, "onCreate in ConversationViewFragment (this=%s)", this); 240f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // Not really, we just want to get a crack to store a reference to the change_folder item 241f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp setHasOptionsMenu(true); 2429d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 2439d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (savedState != null) { 2449d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang mViewState = savedState.getParcelable(BUNDLE_VIEW_STATE); 2459d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang mUserVisible = savedState.getBoolean(BUNDLE_USER_VISIBLE); 24618176786f320b4d4312ad1682da057434f201c13Scott Kennedy mIsDetached = savedState.getBoolean(BUNDLE_DETACHED, false); 2472fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationBeenTransformed = 2482fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein savedState.getBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_TRANSFORMED, false); 2492fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationTransformBeenReverted = 2502fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein savedState.getBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_REVERTED, false); 2519d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } else { 2529d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang mViewState = getNewViewState(); 2532fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationBeenTransformed = false; 2542fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationTransformBeenReverted = false; 2559d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 256f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 257f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 258606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein /** 259606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein * Can be overridden in case a subclass needs to get additional arguments. 260606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein */ 261606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein protected void parseArguments() { 262606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein final Bundle args = getArguments(); 263606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein mAccount = args.getParcelable(ARG_ACCOUNT); 264606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein mConversation = args.getParcelable(ARG_CONVERSATION); 265606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein } 266606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein 267606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein /** 268606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein * Can be overridden in case a subclass needs a different uri format 269606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein * (such as one that does not rely on account and/or conversation. 270606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein */ 271606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein protected void setBaseUri() { 272ee04e666deb8bb194f742f762462fc043733d666Ray Chen mBaseUri = buildBaseUri(getContext(), mAccount, mConversation); 273562c5ba7235948cf1d20a9afa40e67cd62f43cf7Andrew Sapperstein } 274562c5ba7235948cf1d20a9afa40e67cd62f43cf7Andrew Sapperstein 2754b0c0127d24e387a4f49d442b63b5c60cedb6922Ray Chen public static String buildBaseUri(Context context, Account account, Conversation conversation) { 276606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein // Since the uri specified in the conversation base uri may not be unique, we specify a 277606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein // base uri that us guaranteed to be unique for this conversation. 278fd79237327f6803675cad2a64a74411ebda854e3Paul Westbrook return "x-thread://" + account.getAccountId().hashCode() + "/" + conversation.id; 279606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein } 280606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein 2819e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang @Override 2829e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang public String toString() { 2839e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang // log extra info at DEBUG level or finer 2849e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang final String s = super.toString(); 2859e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang if (!LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG) || mConversation == null) { 2869e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang return s; 2879e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang } 2889e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang return "(" + s + " conv=" + mConversation + ")"; 2899e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang } 2909e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang 291f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 292f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public void onActivityCreated(Bundle savedInstanceState) { 293f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp super.onActivityCreated(savedInstanceState); 2948fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal final Activity activity = getActivity(); 295f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (!(activity instanceof ControllableActivity)) { 296f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.wtf(LOG_TAG, "ConversationViewFragment expects only a ControllableActivity to" 297f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp + "create it. Cannot proceed."); 298f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 2998fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal if (activity == null || activity.isFinishing()) { 300f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // Activity is finishing, just bail. 301f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return; 302f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 3038fe8ed43ef6e08b09d12b0827607d15699da3c06Vikram Aggarwal mActivity = (ControllableActivity) activity; 304f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mContext = activity.getApplicationContext(); 305b622d2b6750423c185a2a2463277e5e2b853fadaAndy Huang mWebViewClient.setActivity(activity); 306f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mAccount = mAccountObserver.initialize(mActivity.getAccountController()); 307376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mWebViewClient.setAccount(mAccount); 308f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 309f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 310f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 311f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public ConversationUpdater getListController() { 312f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp final ControllableActivity activity = (ControllableActivity) getActivity(); 313f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return activity != null ? activity.getConversationUpdater() : null; 314f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 315f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 316f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public Context getContext() { 317f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return mContext; 318f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 319f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 32002133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang @Override 321f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public Conversation getConversation() { 322f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return mConversation; 323f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 324f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 325f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 3263f6111cf87080fe7adba512c7b9dfad2e4cd8154James Lemieux public @Nullable MessageCursor getMessageCursor() { 327f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return mCursor; 328f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 329f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 330ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp public Handler getHandler() { 331ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp return mHandler; 332ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp } 333ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp 334f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public MessageLoaderCallbacks getMessageLoaderCallbacks() { 335f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return mMessageLoaderCallbacks; 336f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 337f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 338f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public ContactLoaderCallbacks getContactInfoSource() { 339376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein if (mContactLoaderCallbacks == null) { 3408913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein mContactLoaderCallbacks = mActivity.getContactLoaderCallbacks(); 341376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein } 342f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return mContactLoaderCallbacks; 343f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 344f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 345f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 346f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public Account getAccount() { 347f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return mAccount; 348f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 349f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 350f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 351f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 352f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp super.onCreateOptionsMenu(menu, inflater); 3536c570db71b8adc43d7dc2f31e6fa63cf50c1426eAndrew Sapperstein mChangeFoldersMenuItem = menu.findItem(R.id.change_folders); 354f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 355f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 356f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 357f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public boolean onOptionsItemSelected(MenuItem item) { 358bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang if (!isUserVisible()) { 359bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang // Unclear how this is happening. Current theory is that this fragment was scheduled 360bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang // to be removed, but the remove transaction failed. When the Activity is later 361bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang // restored, the FragmentManager restores this fragment, but Fragment.mMenuVisible is 362bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang // stuck at its initial value (true), which makes this zombie fragment eligible for 363bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang // menu item clicks. 364bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang // 365bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang // Work around this by relying on the (properly restored) extra user visible hint. 366bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang LogUtils.e(LOG_TAG, 367bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang "ACVF ignoring onOptionsItemSelected b/c userVisibleHint is false. f=%s", this); 368bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) { 36954a9057dade988a0fd2ff585cf477492d86669e8Tony Mantler LogUtils.e(LOG_TAG, "%s", Utils.dumpFragment(this)); 370bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang } 371bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang return false; 372bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang } 373bb9dd6b315c2b3a5816c8f52418a991c5c1b70fdAndy Huang 374a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao boolean handled = true; 3752b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy final int itemId = item.getItemId(); 376a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao if (itemId == R.id.inside_conversation_unread || itemId == R.id.toggle_read_unread) { 3772b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy markUnread(); 37887a8982af83168453b6dc219fe984bff68047bebAndy Huang } else if (itemId == R.id.read) { 37987a8982af83168453b6dc219fe984bff68047bebAndy Huang markRead(true /* force */); 38087a8982af83168453b6dc219fe984bff68047bebAndy Huang mActivity.supportInvalidateOptionsMenu(); 3812b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy } else if (itemId == R.id.show_original) { 3822b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy showUntransformedConversation(); 3836293ef09d934e191148279a72036f927366f82f0Andrew Sapperstein } else if (itemId == R.id.print_all) { 3845c1692a5faeab220881a17a3427a8986ef874403Andrew Sapperstein printConversation(); 385a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao } else if (itemId == R.id.reply) { 386a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao handleReply(); 387a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao } else if (itemId == R.id.reply_all) { 388a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao handleReplyAll(); 389a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao } else { 390a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao handled = false; 391f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 392f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return handled; 393f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 394f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 3952fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein @Override 3962fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein public void onPrepareOptionsMenu(Menu menu) { 3972fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein // Only show option if we support message transforms and message has been transformed. 3981e5bccefa627306120fbfd783838e1ef8b7fe0d8Jin Cao Utils.setMenuItemPresent(menu, R.id.show_original, supportsMessageTransforms() && 3992fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationBeenTransformed && !mHasConversationTransformBeenReverted); 4007cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux 4017cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux final MenuItem printMenuItem = menu.findItem(R.id.print_all); 4027cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux if (printMenuItem != null) { 4037cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux // compute the visibility of the print menu item 4047cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux printMenuItem.setVisible(Utils.isRunningKitkatOrLater() && shouldShowPrintInOverflow()); 4057cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux 4067cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux // compute the text displayed on the print menu item 4077cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux if (mConversation.getNumMessages() == 1) { 4087cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux printMenuItem.setTitle(R.string.print); 4097cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux } else { 4107cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux printMenuItem.setTitle(R.string.print_all); 4117cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux } 4127cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux } 4132fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein } 4142fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein 4154ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein abstract boolean supportsMessageTransforms(); 4164ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein 417f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // BEGIN conversation header callbacks 418f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 419f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public void onFoldersClicked() { 420f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (mChangeFoldersMenuItem == null) { 421f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.e(LOG_TAG, "unable to open 'change folders' dialog for a conversation"); 422f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return; 423f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 424f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mActivity.onOptionsItemSelected(mChangeFoldersMenuItem); 425f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 426f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // END conversation header callbacks 427f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 428f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 429761522c1ac7a8fa05acde714f0e26fde47f3415fAndy Huang public void onStart() { 430761522c1ac7a8fa05acde714f0e26fde47f3415fAndy Huang super.onStart(); 431761522c1ac7a8fa05acde714f0e26fde47f3415fAndy Huang 432761522c1ac7a8fa05acde714f0e26fde47f3415fAndy Huang Analytics.getInstance().sendView(getClass().getName()); 433761522c1ac7a8fa05acde714f0e26fde47f3415fAndy Huang } 434761522c1ac7a8fa05acde714f0e26fde47f3415fAndy Huang 435761522c1ac7a8fa05acde714f0e26fde47f3415fAndy Huang @Override 4369d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang public void onSaveInstanceState(Bundle outState) { 4379d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (mViewState != null) { 4389d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang outState.putParcelable(BUNDLE_VIEW_STATE, mViewState); 4399d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 4409d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang outState.putBoolean(BUNDLE_USER_VISIBLE, mUserVisible); 44118176786f320b4d4312ad1682da057434f201c13Scott Kennedy outState.putBoolean(BUNDLE_DETACHED, mIsDetached); 4422fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein outState.putBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_TRANSFORMED, 4432fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationBeenTransformed); 4442fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein outState.putBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_REVERTED, 4452fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationTransformBeenReverted); 4469d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 4479d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 4489d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang @Override 449f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public void onDestroyView() { 450f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp super.onDestroyView(); 451f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mAccountObserver.unregisterAndDestroy(); 452f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 453f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 454f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp /** 455f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * {@link #setUserVisibleHint(boolean)} only works on API >= 15, so implement our own for 456f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * reliability on older platforms. 457f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp */ 458f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public void setExtraUserVisibleHint(boolean isVisibleToUser) { 459f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.v(LOG_TAG, "in CVF.setHint, val=%s (%s)", isVisibleToUser, this); 460f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (mUserVisible != isVisibleToUser) { 461f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mUserVisible = isVisibleToUser; 462bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp MessageCursor cursor = getMessageCursor(); 4630b9b48c1ac510d6d0d043a86354880ceb57a8175mindyp if (mUserVisible && (cursor != null && cursor.isLoaded() && cursor.getCount() == 0)) { 464bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // Pop back to conversation list and show error. 465bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp onError(); 466bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp return; 467bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp } 468f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp onUserVisibleHintChanged(); 469f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 470f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 471f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 4729d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang public boolean isUserVisible() { 4739d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang return mUserVisible; 4749d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 4759d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 476243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang protected void timerMark(String msg) { 477243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang if (isUserVisible()) { 478243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang Utils.sConvLoadTimer.mark(msg); 479243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang } 480243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang } 481243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang 482c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook private class MessageLoaderCallbacks 483c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook implements LoaderManager.LoaderCallbacks<ObjectCursor<ConversationMessage>> { 484f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 485f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 486c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook public Loader<ObjectCursor<ConversationMessage>> onCreateLoader(int id, Bundle args) { 48702133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang return new MessageLoader(mActivity.getActivityContext(), mConversation.messageListUri); 488f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 489f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 490f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 491c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook public void onLoadFinished(Loader<ObjectCursor<ConversationMessage>> loader, 492c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook ObjectCursor<ConversationMessage> data) { 493f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // ignore truly duplicate results 494f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // this can happen when restoring after rotation 495f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (mCursor == data) { 496f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return; 497f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } else { 4986766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang final MessageCursor messageCursor = (MessageCursor) data; 499f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 50002133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang // bind the cursor to this fragment so it can access to the current list controller 50102133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang messageCursor.setController(AbstractConversationViewFragment.this); 50202133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang 503f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) { 504f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.d(LOG_TAG, "LOADED CONVERSATION= %s", messageCursor.getDebugDump()); 505f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 506f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 507a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal // We have no messages: exit conversation view. 508a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal if (messageCursor.getCount() == 0 50918176786f320b4d4312ad1682da057434f201c13Scott Kennedy && (!CursorStatus.isWaitingForResults(messageCursor.getStatus()) 51018176786f320b4d4312ad1682da057434f201c13Scott Kennedy || mIsDetached)) { 511f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (mUserVisible) { 512bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp onError(); 513f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } else { 514f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // we expect that the pager adapter will remove this 515f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // conversation fragment on its own due to a separate 516f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // conversation cursor update (we might get here if the 517f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // message list update fires first. nothing to do 518f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // because we expect to be torn down soon.) 519f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.i(LOG_TAG, "CVF: offscreen conv has no messages, ignoring update" 520c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook + " in anticipation of conv cursor update. c=%s", 521c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook mConversation.uri); 522f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 523233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang // existing mCursor will imminently be closed, must stop referencing it 524233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang // since we expect to be kicked out soon, it doesn't matter what mCursor 525233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang // becomes 526233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang mCursor = null; 527f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return; 528f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 529f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 530f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // ignore cursors that are still loading results 531f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (!messageCursor.isLoaded()) { 532233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang // existing mCursor will imminently be closed, must stop referencing it 533233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang // in this case, the new cursor is also no good, and since don't expect to get 534233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang // here except in initial load situations, it's safest to just ensure the 535233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang // reference is null 536233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang mCursor = null; 537f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return; 538f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 539014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang final MessageCursor oldCursor = mCursor; 5406766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang mCursor = messageCursor; 541014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang onMessageCursorLoadFinished(loader, mCursor, oldCursor); 542f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 543f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 544f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 545f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 546c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook public void onLoaderReset(Loader<ObjectCursor<ConversationMessage>> loader) { 547f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mCursor = null; 548f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 549f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 550f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 551f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 552bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp private void onError() { 553bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // need to exit this view- conversation may have been 554bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // deleted, or for whatever reason is now invalid (e.g. 555bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // discard single draft) 556bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // 557bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // N.B. this may involve a fragment transaction, which 558bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // FragmentManager will refuse to execute directly 559bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // within onLoadFinished. Make sure the controller knows. 560bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp LogUtils.i(LOG_TAG, "CVF: visible conv has no messages, exiting conv mode"); 561bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // TODO(mindyp): handle ERROR status by showing an error 562bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // message to the user that there are no messages in 563bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp // this conversation 56418176786f320b4d4312ad1682da057434f201c13Scott Kennedy popOut(); 56518176786f320b4d4312ad1682da057434f201c13Scott Kennedy } 566bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp 56718176786f320b4d4312ad1682da057434f201c13Scott Kennedy private void popOut() { 568376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mHandler.post(new FragmentRunnable("popOut", this) { 569bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp @Override 5709a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang public void go() { 5717729a9aaa1873505191303a35c480b6dcf1c7382Alice Yang if (mActivity != null) { 5727729a9aaa1873505191303a35c480b6dcf1c7382Alice Yang mActivity.getListHandler() 5737729a9aaa1873505191303a35c480b6dcf1c7382Alice Yang .onConversationSelected(null, true /* inLoaderCallbacks */); 5747729a9aaa1873505191303a35c480b6dcf1c7382Alice Yang } 575bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp } 576bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp }); 577bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp } 578bc4142f40e3bce1b32bcf6e890d5040c214ac2e7mindyp 579e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang /** 580e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang * @see Folder#getTypeDescription() 581e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang */ 582e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang protected String getCurrentFolderTypeDesc() { 583e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang final Folder currFolder; 584e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang if (mActivity != null) { 585e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang currFolder = mActivity.getFolderController().getFolder(); 586e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang } else { 587e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang currFolder = null; 588e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang } 589e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang final String folderStr; 590e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang if (currFolder != null) { 591e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang folderStr = currFolder.getTypeDescription(); 592e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang } else { 593e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang folderStr = "unknown_folder"; 594e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang } 595e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang return folderStr; 596e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang } 597e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang 598e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang private void logConversationView() { 599e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang final String folderStr = getCurrentFolderTypeDesc(); 600e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang Analytics.getInstance().sendEvent("view_conversation", folderStr, 601e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang mConversation.isRemote ? "unsynced" : "synced", mConversation.getNumMessages()); 602e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang } 603e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang 60487a8982af83168453b6dc219fe984bff68047bebAndy Huang protected final void onConversationSeen() { 605919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy LogUtils.d(LOG_TAG, "AbstractConversationViewFragment#onConversationSeen()"); 606919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy 6074d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook // Ignore unsafe calls made after a fragment is detached from an activity 6084d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook final ControllableActivity activity = (ControllableActivity) getActivity(); 6094d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook if (activity == null) { 6104d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook LogUtils.w(LOG_TAG, "ignoring onConversationSeen for conv=%s", mConversation.id); 6114d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook return; 6124d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook } 6134d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook 614e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang // this method is called 2x on rotation; debounce this a bit so as not to 615e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang // dramatically skew analytics data too much. Ideally, it should be called zero times 616e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang // on rotation... 617e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang if (!mConversationSeen) { 618e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang logConversationView(); 619e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang } 620e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang 6214d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook mViewState.setInfoForConversation(mConversation); 6224d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook 623919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy LogUtils.d(LOG_TAG, "onConversationSeen() - mSuppressMarkingViewed = %b", 624919d01a1144dc14f114860949ff4af79a9165ec7Scott Kennedy mSuppressMarkingViewed); 625d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal // In most circumstances we want to mark the conversation as viewed and read, since the 626d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal // user has read it. However, if the user has already marked the conversation unread, we 627d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal // do not want a later mark-read operation to undo this. So we check this variable which 628d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal // is set in #markUnread() which suppresses automatic mark-read. 629d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal if (!mSuppressMarkingViewed) { 63087a8982af83168453b6dc219fe984bff68047bebAndy Huang markRead(false /* force */); 6314d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook } 6323b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy activity.getListHandler().onConversationSeen(); 633e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang 634e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang mConversationSeen = true; 6354d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook } 6364d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook 6374d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook protected ConversationViewState getNewViewState() { 6384d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook return new ConversationViewState(); 6394d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook } 6404d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook 641c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook private static class MessageLoader extends ObjectCursorLoader<ConversationMessage> { 642f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp private boolean mDeliveredFirstResults = false; 643f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 64402133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang public MessageLoader(Context c, Uri messageListUri) { 645c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook super(c, messageListUri, UIProvider.MESSAGE_PROJECTION, ConversationMessage.FACTORY); 646f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 647f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 648f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 649c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook public void deliverResult(ObjectCursor<ConversationMessage> result) { 650f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // We want to deliver these results, and then we want to make sure 651f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // that any subsequent 652f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // queries do not hit the network 653f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp super.deliverResult(result); 654f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 655f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (!mDeliveredFirstResults) { 656f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mDeliveredFirstResults = true; 657f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp Uri uri = getUri(); 658f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 659f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // Create a ListParams that tells the provider to not hit the 660f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // network 661f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp final ListParams listParams = new ListParams(ListParams.NO_LIMIT, 662f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp false /* useNetwork */); 663f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 664f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // Build the new uri with this additional parameter 665f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp uri = uri 666f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp .buildUpon() 667f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp .appendQueryParameter(UIProvider.LIST_PARAMS_QUERY_PARAMETER, 668f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp listParams.serialize()).build(); 669f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp setUri(uri); 670f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 671f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 672c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook 673c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook @Override 674c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook protected ObjectCursor<ConversationMessage> getObjectCursor(Cursor inner) { 675c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook return new MessageCursor(inner); 676c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook } 677f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 678f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 67926d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp public abstract void onConversationUpdated(Conversation conversation); 68026d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp 68118176786f320b4d4312ad1682da057434f201c13Scott Kennedy public void onDetachedModeEntered() { 68218176786f320b4d4312ad1682da057434f201c13Scott Kennedy // If we have no messages, then we have nothing to display, so leave this view. 68318176786f320b4d4312ad1682da057434f201c13Scott Kennedy // Otherwise, just set the detached flag. 68418176786f320b4d4312ad1682da057434f201c13Scott Kennedy final Cursor messageCursor = getMessageCursor(); 68518176786f320b4d4312ad1682da057434f201c13Scott Kennedy 68618176786f320b4d4312ad1682da057434f201c13Scott Kennedy if (messageCursor == null || messageCursor.getCount() == 0) { 68718176786f320b4d4312ad1682da057434f201c13Scott Kennedy popOut(); 68818176786f320b4d4312ad1682da057434f201c13Scott Kennedy } else { 68918176786f320b4d4312ad1682da057434f201c13Scott Kennedy mIsDetached = true; 69018176786f320b4d4312ad1682da057434f201c13Scott Kennedy } 69118176786f320b4d4312ad1682da057434f201c13Scott Kennedy } 6922fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein 6932fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein /** 6942fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * Called when the JavaScript reports that it transformed a message. 6952fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * Sets a flag to true and invalidates the options menu so it will 6962fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * include the "Revert auto-sizing" menu option. 6972fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein */ 6982fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein public void onConversationTransformed() { 6992fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationBeenTransformed = true; 700376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mHandler.post(new FragmentRunnable("invalidateOptionsMenu", this) { 701ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein @Override 702ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein public void go() { 70352882ff08fe9420fe2dbb3f681545a330222e9c8Andrew Sapperstein mActivity.supportInvalidateOptionsMenu(); 704ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein } 705ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein }); 7062fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein } 7072fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein 7082fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein /** 7092fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * Called when the "Revert auto-sizing" option is selected. Default 7102fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * implementation simply sets a value on whether transforms should be 7112fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * applied. Derived classes should override this class and force a 7122fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * re-render so that the conversation renders without 7132fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein */ 7142fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein public void showUntransformedConversation() { 7152fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein // must set the value to true so we don't show the options menu item again 7162fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein mHasConversationTransformBeenReverted = true; 7172fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein } 7182fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein 7192fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein /** 7202fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * Returns {@code true} if the conversation should be transformed. {@code false}, otherwise. 7212fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein * @return {@code true} if the conversation should be transformed. {@code false}, otherwise. 7222fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein */ 7232fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein public boolean shouldApplyTransforms() { 7243617b41394337774fb2e6fcf9fef1ac9eca00482Alice Yang return (mAccount.enableMessageTransforms > 0) && 7253617b41394337774fb2e6fcf9fef1ac9eca00482Alice Yang !mHasConversationTransformBeenReverted; 7262fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein } 7275c1692a5faeab220881a17a3427a8986ef874403Andrew Sapperstein 7287cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux /** 7297cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux * The Print item in the overflow menu of the Conversation view is shown based on the return 7307cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux * from this method. 7317cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux * 7327cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux * @return {@code true} if the conversation can be printed; {@code false} otherwise. 7337cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux */ 7347cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux protected abstract boolean shouldShowPrintInOverflow(); 7357cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux 7367cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux /** 7377cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux * Prints all messages in the conversation. 7387cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux */ 7395c1692a5faeab220881a17a3427a8986ef874403Andrew Sapperstein protected abstract void printConversation(); 7408ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein 741a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao // These methods should perform default reply/replyall action on the last message. 742a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao protected abstract void handleReply(); 743a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao protected abstract void handleReplyAll(); 744a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao 7458ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein public boolean shouldAlwaysShowImages() { 7468ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein return (mAccount != null) && (mAccount.settings.showImages == Settings.ShowImages.ALWAYS); 7478ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 748f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp} 749