1c8a994227b9c686d88ee05840544162711a85712Marc Blank/******************************************************************************* 2c8a994227b9c686d88ee05840544162711a85712Marc Blank * Copyright (C) 2012 Google Inc. 3c8a994227b9c686d88ee05840544162711a85712Marc Blank * Licensed to The Android Open Source Project. 4c8a994227b9c686d88ee05840544162711a85712Marc Blank * 5c8a994227b9c686d88ee05840544162711a85712Marc Blank * Licensed under the Apache License, Version 2.0 (the "License"); 6c8a994227b9c686d88ee05840544162711a85712Marc Blank * you may not use this file except in compliance with the License. 7c8a994227b9c686d88ee05840544162711a85712Marc Blank * You may obtain a copy of the License at 8c8a994227b9c686d88ee05840544162711a85712Marc Blank * 9c8a994227b9c686d88ee05840544162711a85712Marc Blank * http://www.apache.org/licenses/LICENSE-2.0 10c8a994227b9c686d88ee05840544162711a85712Marc Blank * 11c8a994227b9c686d88ee05840544162711a85712Marc Blank * Unless required by applicable law or agreed to in writing, software 12c8a994227b9c686d88ee05840544162711a85712Marc Blank * distributed under the License is distributed on an "AS IS" BASIS, 13c8a994227b9c686d88ee05840544162711a85712Marc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14c8a994227b9c686d88ee05840544162711a85712Marc Blank * See the License for the specific language governing permissions and 15c8a994227b9c686d88ee05840544162711a85712Marc Blank * limitations under the License. 16c8a994227b9c686d88ee05840544162711a85712Marc Blank *******************************************************************************/ 17c8a994227b9c686d88ee05840544162711a85712Marc Blank 18c8a994227b9c686d88ee05840544162711a85712Marc Blankpackage com.android.mail.browse; 19c8a994227b9c686d88ee05840544162711a85712Marc Blank 2048eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blankimport android.app.Activity; 21c8a994227b9c686d88ee05840544162711a85712Marc Blankimport android.content.ContentProvider; 228d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blankimport android.content.ContentProviderOperation; 23c8a994227b9c686d88ee05840544162711a85712Marc Blankimport android.content.ContentResolver; 24c8a994227b9c686d88ee05840544162711a85712Marc Blankimport android.content.ContentValues; 25bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrookimport android.content.Context; 268d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blankimport android.content.OperationApplicationException; 2748eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blankimport android.database.CharArrayBuffer; 28c8a994227b9c686d88ee05840544162711a85712Marc Blankimport android.database.ContentObserver; 29c8a994227b9c686d88ee05840544162711a85712Marc Blankimport android.database.Cursor; 3048eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blankimport android.database.DataSetObserver; 31c8a994227b9c686d88ee05840544162711a85712Marc Blankimport android.net.Uri; 32e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blankimport android.os.AsyncTask; 3348eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blankimport android.os.Bundle; 34e77d5262a98d28de198c06c6ef84b6b70047a60aMarc Blankimport android.os.Handler; 358d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blankimport android.os.Looper; 368d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blankimport android.os.RemoteException; 371bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huangimport android.os.SystemClock; 3808a079c3d2857e365736432b2691187767eb116fScott Kennedyimport android.support.v4.util.SparseArrayCompat; 39cb0b30ee1d5cfcc267bc7de1e6ad78ed766c1e50mindypimport android.text.TextUtils; 40c8a994227b9c686d88ee05840544162711a85712Marc Blank 416de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrookimport com.android.mail.content.ThreadSafeCursorWrapper; 42f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blankimport com.android.mail.providers.Conversation; 4326746eb4acb159f3ce0d411c10d85a1de3e958a0Paul Westbrookimport com.android.mail.providers.Folder; 44b2033d855ab0f13e253e5403ce25989bcbc49488Andy Huangimport com.android.mail.providers.FolderList; 454015c182ab04edaa7cd8b75490f4336348ec29daMarc Blankimport com.android.mail.providers.UIProvider; 4651144944c2088299ebc2584cc09c61c23eb18659Marc Blankimport com.android.mail.providers.UIProvider.ConversationListQueryParameters; 47334e64af904085984cdcbecbcbc18cf488a9ceaePaul Westbrookimport com.android.mail.providers.UIProvider.ConversationOperations; 4881a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwalimport com.android.mail.ui.ConversationListFragment; 49144bfe739b93afdee0a1700a34806b0b287e5887Andy Huangimport com.android.mail.utils.DrawIdler; 5003fa19afc40791aec7662b2db525c63e78808053Marc Blankimport com.android.mail.utils.LogUtils; 5108a079c3d2857e365736432b2691187767eb116fScott Kennedyimport com.android.mail.utils.NotificationActionUtils; 5208a079c3d2857e365736432b2691187767eb116fScott Kennedyimport com.android.mail.utils.NotificationActionUtils.NotificationAction; 5308a079c3d2857e365736432b2691187767eb116fScott Kennedyimport com.android.mail.utils.NotificationActionUtils.NotificationActionType; 54f74147ff72429681d488a75166ec42d434456456mindypimport com.android.mail.utils.Utils; 55248b1b49455785aa2fa426bc28090547abfcb01aMarc Blankimport com.google.common.annotations.VisibleForTesting; 561bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huangimport com.google.common.collect.ImmutableSet; 57bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrookimport com.google.common.collect.Lists; 583d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrookimport com.google.common.collect.Maps; 59fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrookimport com.google.common.collect.Sets; 60f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blank 6197bca7b52aa2840494753d700a641161099cde23Marc Blankimport java.util.ArrayList; 62bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrookimport java.util.Arrays; 63bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrookimport java.util.Collection; 641bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huangimport java.util.Collections; 65c8a994227b9c686d88ee05840544162711a85712Marc Blankimport java.util.HashMap; 6648eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blankimport java.util.Iterator; 67c8a994227b9c686d88ee05840544162711a85712Marc Blankimport java.util.List; 68c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrookimport java.util.Map; 69fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrookimport java.util.Set; 70c8a994227b9c686d88ee05840544162711a85712Marc Blank 71c8a994227b9c686d88ee05840544162711a85712Marc Blank/** 72c8a994227b9c686d88ee05840544162711a85712Marc Blank * ConversationCursor is a wrapper around a conversation list cursor that provides update/delete 73c8a994227b9c686d88ee05840544162711a85712Marc Blank * caching for quick UI response. This is effectively a singleton class, as the cache is 74c8a994227b9c686d88ee05840544162711a85712Marc Blank * implemented as a static HashMap. 75c8a994227b9c686d88ee05840544162711a85712Marc Blank */ 76144bfe739b93afdee0a1700a34806b0b287e5887Andy Huangpublic final class ConversationCursor implements Cursor, ConversationCursorOperationListener, 77144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang DrawIdler.IdleListener { 781bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang 79c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang public static final String LOG_TAG = "ConvCursor"; 808d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** Turn to true for debugging. */ 818d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal private static final boolean DEBUG = false; 828d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** A deleted row is indicated by the presence of DELETED_COLUMN in the cache map */ 83c8a994227b9c686d88ee05840544162711a85712Marc Blank private static final String DELETED_COLUMN = "__deleted__"; 848d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** An row cached during a requery is indicated by the presence of REQUERY_COLUMN in the map */ 857f55c685376659550ed11b047a78cd8d70158ad9mindyp private static final String UPDATE_TIME_COLUMN = "__updatetime__"; 868d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** 878d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal * A sentinel value for the "index" of the deleted column; it's an int that is otherwise invalid 888d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal */ 89c8a994227b9c686d88ee05840544162711a85712Marc Blank private static final int DELETED_COLUMN_INDEX = -1; 908d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** 918d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal * If a cached value within 10 seconds of a refresh(), preserve it. This time has been 928d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal * chosen empirically (long enough for UI changes to propagate in any reasonable case) 938d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal */ 947f55c685376659550ed11b047a78cd8d70158ad9mindyp private static final long REQUERY_ALLOWANCE_TIME = 10000L; 958d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal 968d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** 978d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal * The index of the Uri whose data is reflected in the cached row. Updates/Deletes to this Uri 988d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal * are cached 998d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal */ 1008d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal private static final int URI_COLUMN_INDEX = UIProvider.CONVERSATION_URI_COLUMN; 1018d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal 1024fcac283dfba95f1a89c1ea9c3c5240560378b94Paul Westbrook private static final boolean DEBUG_DUPLICATE_KEYS = true; 1033d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook 1047460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal /** The resolver for the cursor instantiator's context */ 1057460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal private final ContentResolver mResolver; 1067460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal 1078d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** Our sequence count (for changes sent to underlying provider) */ 1088d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal private static int sSequence = 0; 109cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank @VisibleForTesting 110cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank static ConversationProvider sProvider; 111bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 1128d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** The cursor underlying the caching cursor */ 113bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook @VisibleForTesting 1140a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook UnderlyingCursorWrapper mUnderlyingCursor; 1158d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** The new cursor obtained via a requery */ 1160a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook private volatile UnderlyingCursorWrapper mRequeryCursor; 1178d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** A mapping from Uri to updated ContentValues */ 1188d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal private final HashMap<String, ContentValues> mCacheMap = new HashMap<String, ContentValues>(); 1198d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** Cache map lock (will be used only very briefly - few ms at most) */ 1208d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal private final Object mCacheMapLock = new Object(); 1218d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** The listeners registered for this cursor */ 1228d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal private final List<ConversationListener> mListeners = Lists.newArrayList(); 1238d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** 1248d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal * The ConversationProvider instance // The runnable executing a refresh (query of underlying 1258d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal * provider) 1268d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal */ 127bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook private RefreshTask mRefreshTask; 1288d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** Set when we've sent refreshReady() to listeners */ 129bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook private boolean mRefreshReady = false; 1308d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** Set when we've sent refreshRequired() to listeners */ 131bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook private boolean mRefreshRequired = false; 1328d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** Whether our first query on this cursor should include a limit */ 1335bb4d053519be10324752f323bcf73f13b9a2604Andrew Sapperstein private boolean mUseInitialConversationLimit = false; 1348d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** A list of mostly-dead items */ 1358d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal private final List<Conversation> mMostlyDead = Lists.newArrayList(); 136828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein /** A list of items pending removal from a notification action. These may be undone later. 137828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein * Note: only modify on UI thread. */ 13808a079c3d2857e365736432b2691187767eb116fScott Kennedy private final Set<Conversation> mNotificationTempDeleted = Sets.newHashSet(); 1398d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** The name of the loader */ 140bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook private final String mName; 1418d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** Column names for this cursor */ 142bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook private String[] mColumnNames; 1431bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang // Column names as above, as a Set for quick membership checking 1441bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang private Set<String> mColumnNameSet; 1458d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** An observer on the underlying cursor (so we can detect changes from outside the UI) */ 146c8a994227b9c686d88ee05840544162711a85712Marc Blank private final CursorObserver mCursorObserver; 1478d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** Whether our observer is currently registered with the underlying cursor */ 14897bca7b52aa2840494753d700a641161099cde23Marc Blank private boolean mCursorObserverRegistered = false; 1498d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** Whether our loader is paused */ 150bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook private boolean mPaused = false; 1518d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** Whether or not sync from underlying provider should be deferred */ 152bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook private boolean mDeferSync = false; 153c8a994227b9c686d88ee05840544162711a85712Marc Blank 1548d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** The current position of the cursor */ 155c8a994227b9c686d88ee05840544162711a85712Marc Blank private int mPosition = -1; 156397621b93f83f8933f7a29a9b6d7fe2b88ec4008Andy Huang 1578d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** 1588d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal * The number of cached deletions from this cursor (used to quickly generate an accurate count) 1598d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal */ 160bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook private int mDeletedCount = 0; 161c8a994227b9c686d88ee05840544162711a85712Marc Blank 1628d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal /** Parameters passed to the underlying query */ 163bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook private Uri qUri; 164bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook private String[] qProjection; 16548eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 16608a079c3d2857e365736432b2691187767eb116fScott Kennedy private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); 16708a079c3d2857e365736432b2691187767eb116fScott Kennedy 1683d5ea425bce684d8fdd0a51f055025a663f3dc2eAlice Yang private final boolean mCachingEnabled; 1693d5ea425bce684d8fdd0a51f055025a663f3dc2eAlice Yang 1700a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook private void setCursor(UnderlyingCursorWrapper cursor) { 171c16be931683b00cc625570f0d188e0981141f965Marc Blank // If we have an existing underlying cursor, make sure it's closed 172bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook if (mUnderlyingCursor != null) { 17304e0dc70c61c1d856877718922f8b1de2f25a2ffPaul Westbrook close(); 174cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank } 175bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mColumnNames = cursor.getColumnNames(); 1761bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang ImmutableSet.Builder<String> builder = ImmutableSet.builder(); 1771bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang for (String name : mColumnNames) { 1781bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang builder.add(name); 1791bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang } 1801bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang mColumnNameSet = builder.build(); 181bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mRefreshRequired = false; 182bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mRefreshReady = false; 183bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mRefreshTask = null; 184bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook resetCursor(cursor); 18508a079c3d2857e365736432b2691187767eb116fScott Kennedy 18608a079c3d2857e365736432b2691187767eb116fScott Kennedy resetNotificationActions(); 18708a079c3d2857e365736432b2691187767eb116fScott Kennedy handleNotificationActions(); 188bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 189bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 1905bb4d053519be10324752f323bcf73f13b9a2604Andrew Sapperstein public ConversationCursor(Activity activity, Uri uri, boolean useInitialConversationLimit, 191bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook String name) { 1925bb4d053519be10324752f323bcf73f13b9a2604Andrew Sapperstein mUseInitialConversationLimit = useInitialConversationLimit; 1937460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal mResolver = activity.getApplicationContext().getContentResolver(); 194bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook qUri = uri; 195bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mName = name; 196bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook qProjection = UIProvider.CONVERSATION_PROJECTION; 197e77d5262a98d28de198c06c6ef84b6b70047a60aMarc Blank mCursorObserver = new CursorObserver(new Handler(Looper.getMainLooper())); 1983d5ea425bce684d8fdd0a51f055025a663f3dc2eAlice Yang 1993d5ea425bce684d8fdd0a51f055025a663f3dc2eAlice Yang // Disable caching on low memory devices 2003d5ea425bce684d8fdd0a51f055025a663f3dc2eAlice Yang mCachingEnabled = !Utils.isLowRamDevice(activity); 201ff5c757ed2bcf7004a70b0675382ef894196558dPaul Westbrook } 202ff5c757ed2bcf7004a70b0675382ef894196558dPaul Westbrook 203ff5c757ed2bcf7004a70b0675382ef894196558dPaul Westbrook /** 20448eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank * Create a ConversationCursor; this should be called by the ListActivity using that cursor 205c8a994227b9c686d88ee05840544162711a85712Marc Blank */ 206bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook public void load() { 207bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook synchronized (mCacheMapLock) { 20851144944c2088299ebc2584cc09c61c23eb18659Marc Blank try { 20951144944c2088299ebc2584cc09c61c23eb18659Marc Blank // Create new ConversationCursor 210d6020119c71ef9fe38725d0f0291cac7b9df6e79Vikram Aggarwal LogUtils.d(LOG_TAG, "Create: initial creation"); 2115bb4d053519be10324752f323bcf73f13b9a2604Andrew Sapperstein setCursor(doQuery(mUseInitialConversationLimit)); 21251144944c2088299ebc2584cc09c61c23eb18659Marc Blank } finally { 21351144944c2088299ebc2584cc09c61c23eb18659Marc Blank // If we used a limit, queue up a query without limit 2145bb4d053519be10324752f323bcf73f13b9a2604Andrew Sapperstein if (mUseInitialConversationLimit) { 2155bb4d053519be10324752f323bcf73f13b9a2604Andrew Sapperstein mUseInitialConversationLimit = false; 2161892261a7dfa66b1c967ede0842a0cb2802f4187Paul Westbrook // We want to notify about this change to allow the UI to requery. We don't 2171892261a7dfa66b1c967ede0842a0cb2802f4187Paul Westbrook // want to directly call refresh() here as this will start an AyncTask which 2181892261a7dfa66b1c967ede0842a0cb2802f4187Paul Westbrook // is normally only run after the cursor is in the "refresh required" 2191892261a7dfa66b1c967ede0842a0cb2802f4187Paul Westbrook // state 2201892261a7dfa66b1c967ede0842a0cb2802f4187Paul Westbrook underlyingChanged(); 221c16be931683b00cc625570f0d188e0981141f965Marc Blank } 222c16be931683b00cc625570f0d188e0981141f965Marc Blank } 223c16be931683b00cc625570f0d188e0981141f965Marc Blank } 224948985b753588aeda2e83aa71eeb738c19963820Marc Blank } 225948985b753588aeda2e83aa71eeb738c19963820Marc Blank 2266ca57e8666f0b5e61c77ff8916b553ebcd07287dMarc Blank /** 227e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank * Pause notifications to UI 228e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank */ 229bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook public void pause() { 2301c3911727b435524e7d4d0cc66ad3522adbd3453Marc Blank mPaused = true; 231c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang if (DEBUG) LogUtils.i(LOG_TAG, "[Paused: %s]", this); 232e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 233e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank 234e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank /** 235e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank * Resume notifications to UI; if any are pending, send them 236e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank */ 237bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook public void resume() { 238bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mPaused = false; 239c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang if (DEBUG) LogUtils.i(LOG_TAG, "[Resumed: %s]", this); 240e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank checkNotifyUI(); 241e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 242e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank 243bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook private void checkNotifyUI() { 244c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang if (DEBUG) LogUtils.i(LOG_TAG, "IN checkNotifyUI, this=%s", this); 245bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook if (!mPaused && !mDeferSync) { 246bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook if (mRefreshRequired && (mRefreshTask == null)) { 247bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook notifyRefreshRequired(); 248bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } else if (mRefreshReady) { 249bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook notifyRefreshReady(); 250e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 251e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 252e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 253e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank 2540a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook public Set<Long> getConversationIds() { 2550a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook return mUnderlyingCursor != null ? mUnderlyingCursor.conversationIds() : null; 2560a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook } 2570a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook 2581bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang private static class UnderlyingRowData { 2591bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang public final String innerUri; 2606de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook public Conversation conversation; 2611bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang 26247cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang public UnderlyingRowData(String innerUri, Conversation conversation) { 2631bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang this.innerUri = innerUri; 2641bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang this.conversation = conversation; 2651bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang } 2661bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang } 2671bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang 268e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank /** 269f74147ff72429681d488a75166ec42d434456456mindyp * Simple wrapper for a cursor that provides methods for quickly determining 270f74147ff72429681d488a75166ec42d434456456mindyp * the existence of a row. 271f74147ff72429681d488a75166ec42d434456456mindyp */ 272144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang private static class UnderlyingCursorWrapper extends ThreadSafeCursorWrapper 273144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang implements DrawIdler.IdleListener { 274144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang 275144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang /** 276144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang * An AsyncTask that will fill as much of the cache as possible until either the cache is 277144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang * full or the task is cancelled. If not cancelled and we're not done caching, it will 278144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang * schedule another iteration to run upon completion. 279144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang * <p> 280144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang * Generally, only one task instance per {@link UnderlyingCursorWrapper} will run at a time. 281144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang * But if an old task is cancelled, it may continue to execute at most one iteration (due 282144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang * to the per-iteration cancellation-signal read), possibly concurrently with a new task. 283144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang */ 2846de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook private class CacheLoaderTask extends AsyncTask<Void, Void, Void> { 2856de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook private final int mStartPos; 2866de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook 2876de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook CacheLoaderTask(int startPosition) { 2886de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook mStartPos = startPosition; 2896de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook } 2906de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook 2916de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook @Override 2926de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook public Void doInBackground(Void... param) { 29343d2cb188e586d84db0db1da3241a615185c7d12Andy Huang try { 29443d2cb188e586d84db0db1da3241a615185c7d12Andy Huang Utils.traceBeginSection("backgroundCaching"); 295144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang if (DEBUG) LogUtils.i(LOG_TAG, "in cache job pos=%s c=%s", mStartPos, 296144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang getWrappedCursor()); 297144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang final int count = getCount(); 298144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang while (true) { 299144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang // It is possible for two instances of this loop to execute at once if 300144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang // an earlier task is cancelled but gets preempted. As written, this loop 301144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang // safely shares mCachePos without mutexes by only reading it once and 302144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang // writing it once (writing based on the previously-read value). 303144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang // The most that can happen is that one row's values is read twice. 304144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang final int pos = mCachePos; 305144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang if (isCancelled() || pos >= count) { 30643d2cb188e586d84db0db1da3241a615185c7d12Andy Huang break; 30743d2cb188e586d84db0db1da3241a615185c7d12Andy Huang } 3086de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook 309144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang final UnderlyingRowData rowData = mRowCache.get(pos); 31043d2cb188e586d84db0db1da3241a615185c7d12Andy Huang if (rowData.conversation == null) { 31143d2cb188e586d84db0db1da3241a615185c7d12Andy Huang // We are running in a background thread. Set the position to the row 31243d2cb188e586d84db0db1da3241a615185c7d12Andy Huang // we are interested in. 313144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang if (moveToPosition(pos)) { 314144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang rowData.conversation = new Conversation( 315144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang UnderlyingCursorWrapper.this); 31643d2cb188e586d84db0db1da3241a615185c7d12Andy Huang } 3176de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook } 318144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang mCachePos = pos + 1; 3196de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook } 320c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang System.gc(); 32143d2cb188e586d84db0db1da3241a615185c7d12Andy Huang } finally { 32243d2cb188e586d84db0db1da3241a615185c7d12Andy Huang Utils.traceEndSection(); 3236de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook } 3246de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook return null; 3256de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook } 326144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang 327144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang @Override 328144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang protected void onPostExecute(Void result) { 329144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang mCacheLoaderTask = null; 330144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang LogUtils.i(LOG_TAG, "ConversationCursor caching complete pos=%s", mCachePos); 331144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang } 332144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang 3336de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook } 3346de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook 3352ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook private class NewCursorUpdateObserver extends ContentObserver { 3362ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook public NewCursorUpdateObserver(Handler handler) { 3372ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook super(handler); 3382ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook } 3392ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook 3402ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook @Override 3412ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook public void onChange(boolean selfChange) { 3422ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook // Since this observer is used to keep track of changes that happen while 3432ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook // the Conversation objects are being pre-cached, and the conversation maps are 3442ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook // populated 3452ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook mCursorUpdated = true; 3462ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook } 3472ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook } 3486de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook 349144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang // be polite by default; assume the device is initially busy and don't start pre-caching 350144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang // until the idler connects and says we're idle 351144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang private int mDrawState = DrawIdler.STATE_ACTIVE; 352144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang /** 353144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang * The one currently active cache task. We try to only run one at a time, but because we 354144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang * don't interrupt the old task when cancelling, it may still run for a bit. See 355144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang * {@link CacheLoaderTask#doInBackground(Void...)} for notes on thread safety. 356144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang */ 357144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang private CacheLoaderTask mCacheLoaderTask; 358144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang /** 359144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang * The current row that the cache task is working on, or should work on next. 360144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang * <p> 361144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang * Not synchronized; see comments in {@link CacheLoaderTask#doInBackground(Void...)} for 362144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang * notes on thread safety. 363144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang */ 364144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang private int mCachePos; 3653d5ea425bce684d8fdd0a51f055025a663f3dc2eAlice Yang private boolean mCachingEnabled; 3662ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook private final NewCursorUpdateObserver mCursorUpdateObserver; 367a3526f6b1f59cc6b83aa6634d6f11e22bdb22edbPaul Westbrook private boolean mUpdateObserverRegistered = false; 3686de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook 3690a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook // Ideally these two objects could be combined into a Map from 3700a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook // conversationId -> position, but the cached values uses the conversation 3710a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook // uri as a key. 372c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook private final Map<String, Integer> mConversationUriPositionMap; 373c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook private final Map<Long, Integer> mConversationIdPositionMap; 3741bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang private final List<UnderlyingRowData> mRowCache; 3750a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook 3762ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook private boolean mCursorUpdated = false; 3772ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook 3783d5ea425bce684d8fdd0a51f055025a663f3dc2eAlice Yang public UnderlyingCursorWrapper(Cursor result, boolean cachingEnabled) { 3790a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook super(result); 3802ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook 3813d5ea425bce684d8fdd0a51f055025a663f3dc2eAlice Yang mCachingEnabled = cachingEnabled; 3823d5ea425bce684d8fdd0a51f055025a663f3dc2eAlice Yang 3832ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook // Register the content observer immediately, as we want to make sure that we don't miss 3842ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook // any updates 3852ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook mCursorUpdateObserver = 3862ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook new NewCursorUpdateObserver(new Handler(Looper.getMainLooper())); 387a3526f6b1f59cc6b83aa6634d6f11e22bdb22edbPaul Westbrook if (result != null) { 388a3526f6b1f59cc6b83aa6634d6f11e22bdb22edbPaul Westbrook result.registerContentObserver(mCursorUpdateObserver); 389a3526f6b1f59cc6b83aa6634d6f11e22bdb22edbPaul Westbrook mUpdateObserverRegistered = true; 390a3526f6b1f59cc6b83aa6634d6f11e22bdb22edbPaul Westbrook } 3912ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook 3923d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook final long start = SystemClock.uptimeMillis(); 39347cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang final Map<String, Integer> uriPositionMap; 39447cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang final Map<Long, Integer> idPositionMap; 3951bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang final UnderlyingRowData[] cache; 3961bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang final int count; 39743d2cb188e586d84db0db1da3241a615185c7d12Andy Huang Utils.traceBeginSection("blockingCaching"); 3983b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy if (super.moveToFirst()) { 3996de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook count = super.getCount(); 4001bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang cache = new UnderlyingRowData[count]; 4011bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang int i = 0; 4023d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook 40347cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang uriPositionMap = Maps.newHashMapWithExpectedSize(count); 40447cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang idPositionMap = Maps.newHashMapWithExpectedSize(count); 4053d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook 406f74147ff72429681d488a75166ec42d434456456mindyp do { 4071bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang final String innerUriString; 4081bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang final long convId; 4091bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang 410144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang innerUriString = super.getString(URI_COLUMN_INDEX); 411144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang convId = super.getLong(UIProvider.CONVERSATION_ID_COLUMN); 4123d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook 4133d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook if (DEBUG_DUPLICATE_KEYS) { 4143d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook if (uriPositionMap.containsKey(innerUriString)) { 4153d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook LogUtils.e(LOG_TAG, "Inserting duplicate conversation uri key: %s. " + 4163d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook "Cursor position: %d, iteration: %d map position: %d", 4173d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook innerUriString, getPosition(), i, 4183d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook uriPositionMap.get(innerUriString)); 4193d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook } 4203d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook if (idPositionMap.containsKey(convId)) { 4213d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook LogUtils.e(LOG_TAG, "Inserting duplicate conversation id key: %d" + 4223d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook "Cursor position: %d, iteration: %d map position: %d", 4233d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook convId, getPosition(), i, idPositionMap.get(convId)); 4243d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook } 4253d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook } 42647cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang 42747cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang uriPositionMap.put(innerUriString, i); 42847cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang idPositionMap.put(convId, i); 42947cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang 4301bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang cache[i] = new UnderlyingRowData( 4311bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang innerUriString, 432144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang null /* conversation */); 4336de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook } while (super.moveToPosition(++i)); 4343d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook 43547cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang if (uriPositionMap.size() != count || idPositionMap.size() != count) { 43647cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang if (DEBUG_DUPLICATE_KEYS) { 43747cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang throw new IllegalStateException("Unexpected map sizes: cursorN=" + count 43847cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang + " uriN=" + uriPositionMap.size() + " idN=" 43947cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang + idPositionMap.size()); 44047cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang } else { 44147cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang LogUtils.e(LOG_TAG, "Unexpected map sizes. Cursor size: %d, " + 44247cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang "uri position map size: %d, id position map size: %d", count, 44347cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang uriPositionMap.size(), idPositionMap.size()); 44447cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang } 4453d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook } 4461bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang } else { 4471bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang count = 0; 4481bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang cache = new UnderlyingRowData[0]; 44947cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang uriPositionMap = Maps.newHashMap(); 45047cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang idPositionMap = Maps.newHashMap(); 451f74147ff72429681d488a75166ec42d434456456mindyp } 45247cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang mConversationUriPositionMap = Collections.unmodifiableMap(uriPositionMap); 45347cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang mConversationIdPositionMap = Collections.unmodifiableMap(idPositionMap); 4543d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook 4551bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang mRowCache = Collections.unmodifiableList(Arrays.asList(cache)); 4563d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook final long end = SystemClock.uptimeMillis(); 457144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang LogUtils.i(LOG_TAG, "*** ConversationCursor pre-loading took %sms n=%s", (end-start), 458144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang count); 4596de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook 46043d2cb188e586d84db0db1da3241a615185c7d12Andy Huang Utils.traceEndSection(); 46143d2cb188e586d84db0db1da3241a615185c7d12Andy Huang 462144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang // Later, when the idler signals that the activity is idle, start a task to cache 463144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang // conversations in pieces. 464144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang mCachePos = 0; 465144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang } 466144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang 467c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang /** 468c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang * Resumes caching at {@link #mCachePos}. 469c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang * 470c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang * @return true if we actually resumed, false if we're done or stopped 471c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang */ 472c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang private boolean resumeCaching() { 473144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang if (mCacheLoaderTask != null) { 474144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang throw new IllegalStateException("unexpected existing task: " + mCacheLoaderTask); 475144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang } 476144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang 477c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang if (mCachingEnabled && mCachePos < getCount()) { 478144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang mCacheLoaderTask = new CacheLoaderTask(mCachePos); 4796de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook mCacheLoaderTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 480c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang return true; 4816de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook } 482c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang return false; 483144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang } 4846de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook 485c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang private void pauseCaching() { 486144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang if (mCacheLoaderTask != null) { 487144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang LogUtils.i(LOG_TAG, "Cancelling caching startPos=%s pos=%s", 488144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang mCacheLoaderTask.mStartPos, mCachePos); 489144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang mCacheLoaderTask.cancel(false /* interrupt */); 490144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang mCacheLoaderTask = null; 491144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang } 492f74147ff72429681d488a75166ec42d434456456mindyp } 493f74147ff72429681d488a75166ec42d434456456mindyp 494c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang public void stopCaching() { 495c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang pauseCaching(); 496c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang mCachingEnabled = false; 497c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang } 498c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang 499f74147ff72429681d488a75166ec42d434456456mindyp public boolean contains(String uri) { 500c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook return mConversationUriPositionMap.containsKey(uri); 501f74147ff72429681d488a75166ec42d434456456mindyp } 502f74147ff72429681d488a75166ec42d434456456mindyp 5030a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook public Set<Long> conversationIds() { 504a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal return mConversationIdPositionMap.keySet(); 505c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook } 506c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook 507c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook public int getPosition(long conversationId) { 508c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook final Integer position = mConversationIdPositionMap.get(conversationId); 509c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook return position != null ? position.intValue() : -1; 510c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook } 511c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook 512c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook public int getPosition(String conversationUri) { 513c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook final Integer position = mConversationUriPositionMap.get(conversationUri); 514c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook return position != null ? position.intValue() : -1; 515f74147ff72429681d488a75166ec42d434456456mindyp } 5161bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang 5171bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang public String getInnerUri() { 5181bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang return mRowCache.get(getPosition()).innerUri; 5191bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang } 5201bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang 5211bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang public Conversation getConversation() { 5226cf45c601317d4b65ffb1896760fa1cb8a2b807cAndy Huang return mRowCache.get(getPosition()).conversation; 5231bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang } 5246de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook 5256de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook public void cacheConversation(Conversation conversation) { 526144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang final UnderlyingRowData rowData = mRowCache.get(getPosition()); 527144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang if (rowData.conversation == null) { 528144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang rowData.conversation = conversation; 5296de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook } 5306de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook } 5316de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook 532983a723931447b6261a2e42b25e6f931dba6de33Paul Westbrook private void notifyConversationUIPositionChange() { 533983a723931447b6261a2e42b25e6f931dba6de33Paul Westbrook Utils.notifyCursorUIPositionChange(this, getPosition()); 534983a723931447b6261a2e42b25e6f931dba6de33Paul Westbrook } 535983a723931447b6261a2e42b25e6f931dba6de33Paul Westbrook 5362ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook /** 5372ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook * Returns a boolean indicating whether the cursor has been updated 5382ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook */ 5392ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook public boolean isDataUpdated() { 5402ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook return mCursorUpdated; 5412ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook } 5422ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook 5432ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook public void disableUpdateNotifications() { 5442ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook if (mUpdateObserverRegistered) { 5452ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook getWrappedCursor().unregisterContentObserver(mCursorUpdateObserver); 5462ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook mUpdateObserverRegistered = false; 5472ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook } 5482ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook } 5492ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook 5506de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook @Override 5516de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook public void close() { 552144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang stopCaching(); 5532ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook disableUpdateNotifications(); 5546de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook super.close(); 5556de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook } 556144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang 557144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang @Override 558144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang public void onStateChanged(DrawIdler idler, int newState) { 559144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang final int oldState = mDrawState; 560144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang mDrawState = newState; 561144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang if (oldState != newState) { 562144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang if (newState == DrawIdler.STATE_IDLE) { 563144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang // begin/resume caching 564c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang final boolean resumed = resumeCaching(); 565c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang if (resumed) { 566144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang LogUtils.i(LOG_TAG, "Resuming caching, pos=%s idler=%s", mCachePos, idler); 567144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang } 568144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang } else { 569144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang // pause caching 570c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang pauseCaching(); 571144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang } 572144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang } 573144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang } 574144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang 575f74147ff72429681d488a75166ec42d434456456mindyp } 576f74147ff72429681d488a75166ec42d434456456mindyp 577f74147ff72429681d488a75166ec42d434456456mindyp /** 578e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank * Runnable that performs the query on the underlying provider 579e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank */ 5803d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook private class RefreshTask extends AsyncTask<Void, Void, UnderlyingCursorWrapper> { 581bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook private RefreshTask() { 582e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank } 583e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank 584e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank @Override 5853d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook protected UnderlyingCursorWrapper doInBackground(Void... params) { 586e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank if (DEBUG) { 5875c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LogUtils.i(LOG_TAG, "[Start refresh of %s: %d]", mName, hashCode()); 588e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank } 589e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank // Get new data 5903d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook final UnderlyingCursorWrapper result = doQuery(false); 5915ef5e0f82082a7e7b31aa185a47843b98ed9afaeMarc Blank // Make sure window is full 5923d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook result.getCount(); 5933d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook return result; 594e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank } 595e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank 596e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank @Override 5973d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook protected void onPostExecute(UnderlyingCursorWrapper result) { 598bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook synchronized(mCacheMapLock) { 5995c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LogUtils.d( 6005c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LOG_TAG, 6015c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp "Received notify ui callback and sending a notification is enabled? %s", 6025c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp (!mPaused && !mDeferSync)); 603e602ae10b24ed61b5fdd651f82b330b7e700d746Marc Blank // If cursor got closed (e.g. reset loader) in the meantime, cancel the refresh 604e602ae10b24ed61b5fdd651f82b330b7e700d746Marc Blank if (isClosed()) { 6053d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook onCancelled(result); 606e602ae10b24ed61b5fdd651f82b330b7e700d746Marc Blank return; 607e602ae10b24ed61b5fdd651f82b330b7e700d746Marc Blank } 6083d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook mRequeryCursor = result; 609bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mRefreshReady = true; 610e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank if (DEBUG) { 6115c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LogUtils.i(LOG_TAG, "[Query done %s: %d]", mName, hashCode()); 612e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank } 6131c3911727b435524e7d4d0cc66ad3522adbd3453Marc Blank if (!mDeferSync && !mPaused) { 614bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook notifyRefreshReady(); 6151c3911727b435524e7d4d0cc66ad3522adbd3453Marc Blank } 616e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank } 617e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank } 618e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank 619e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank @Override 6203d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook protected void onCancelled(UnderlyingCursorWrapper result) { 621e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank if (DEBUG) { 6225c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LogUtils.i(LOG_TAG, "[Ignoring refresh result: %d]", hashCode()); 623e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank } 6243d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook if (result != null) { 6253d39350e5b6c8e6841f4ec40d1fa6cabaf70bd4fPaul Westbrook result.close(); 626e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank } 627e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank } 628e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank } 629e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank 6300a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook private UnderlyingCursorWrapper doQuery(boolean withLimit) { 631bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook Uri uri = qUri; 63251144944c2088299ebc2584cc09c61c23eb18659Marc Blank if (withLimit) { 63351144944c2088299ebc2584cc09c61c23eb18659Marc Blank uri = uri.buildUpon().appendQueryParameter(ConversationListQueryParameters.LIMIT, 63451144944c2088299ebc2584cc09c61c23eb18659Marc Blank ConversationListQueryParameters.DEFAULT_LIMIT).build(); 63551144944c2088299ebc2584cc09c61c23eb18659Marc Blank } 63651144944c2088299ebc2584cc09c61c23eb18659Marc Blank long time = System.currentTimeMillis(); 637dac6580f96f6ef8f6b466e247834a190a56421e7Paul Westbrook 638c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang Utils.traceBeginSection("query"); 6397460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal final Cursor result = mResolver.query(uri, qProjection, null, null, null); 640c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang Utils.traceEndSection(); 6417f55c685376659550ed11b047a78cd8d70158ad9mindyp if (result == null) { 642b184bfe96fa3512af88260fce4f3cee3066fb28dScott Kennedy LogUtils.w(LOG_TAG, "doQuery returning null cursor, uri: " + uri); 64344dc44990d6ef7c2f505c7575117becdeb1f1387Marc Blank } else if (DEBUG) { 64451144944c2088299ebc2584cc09c61c23eb18659Marc Blank time = System.currentTimeMillis() - time; 6455c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LogUtils.i(LOG_TAG, "ConversationCursor query: %s, %dms, %d results", 646dac6580f96f6ef8f6b466e247834a190a56421e7Paul Westbrook uri, time, result.getCount()); 64751144944c2088299ebc2584cc09c61c23eb18659Marc Blank } 648c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang System.gc(); 6493d5ea425bce684d8fdd0a51f055025a663f3dc2eAlice Yang 6503d5ea425bce684d8fdd0a51f055025a663f3dc2eAlice Yang return new UnderlyingCursorWrapper(result, mCachingEnabled); 65148eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 65248eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 6533232a96e0ea88741dc39acf17d49e9c22b61c707Marc Blank static boolean offUiThread() { 6543232a96e0ea88741dc39acf17d49e9c22b61c707Marc Blank return Looper.getMainLooper().getThread() != Thread.currentThread(); 6553232a96e0ea88741dc39acf17d49e9c22b61c707Marc Blank } 6563232a96e0ea88741dc39acf17d49e9c22b61c707Marc Blank 65748eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank /** 65848eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank * Reset the cursor; this involves clearing out our cache map and resetting our various counts 65948eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank * The cursor should be reset whenever we get fresh data from the underlying cursor. The cache 66048eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank * is locked during the reset, which will block the UI, but for only a very short time 66148eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank * (estimated at a few ms, but we can profile this; remember that the cache will usually 66248eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank * be empty or have a few entries) 66348eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank */ 6640a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook private void resetCursor(UnderlyingCursorWrapper newCursorWrapper) { 665bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook synchronized (mCacheMapLock) { 6667f55c685376659550ed11b047a78cd8d70158ad9mindyp // Walk through the cache 667a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal final Iterator<Map.Entry<String, ContentValues>> iter = 6684de145bb568b6963d374fa0bac199218dc494186Paul Westbrook mCacheMap.entrySet().iterator(); 669d9e49da6f1447458e43a8152ba6ae6774b957284Paul Westbrook final long now = System.currentTimeMillis(); 67048eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank while (iter.hasNext()) { 671a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal Map.Entry<String, ContentValues> entry = iter.next(); 672d9e49da6f1447458e43a8152ba6ae6774b957284Paul Westbrook final ContentValues values = entry.getValue(); 673d9e49da6f1447458e43a8152ba6ae6774b957284Paul Westbrook final String key = entry.getKey(); 674f74147ff72429681d488a75166ec42d434456456mindyp boolean withinTimeWindow = false; 675f74147ff72429681d488a75166ec42d434456456mindyp boolean removed = false; 676d9e49da6f1447458e43a8152ba6ae6774b957284Paul Westbrook if (values != null) { 677d9e49da6f1447458e43a8152ba6ae6774b957284Paul Westbrook Long updateTime = values.getAsLong(UPDATE_TIME_COLUMN); 678d9e49da6f1447458e43a8152ba6ae6774b957284Paul Westbrook if (updateTime != null && ((now - updateTime) < REQUERY_ALLOWANCE_TIME)) { 679d6020119c71ef9fe38725d0f0291cac7b9df6e79Vikram Aggarwal LogUtils.d(LOG_TAG, "IN resetCursor, keep recent changes to %s", key); 680f74147ff72429681d488a75166ec42d434456456mindyp withinTimeWindow = true; 681d9e49da6f1447458e43a8152ba6ae6774b957284Paul Westbrook } else if (updateTime == null) { 6825c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LogUtils.e(LOG_TAG, "null updateTime from mCacheMap for key: %s", key); 683d9e49da6f1447458e43a8152ba6ae6774b957284Paul Westbrook } 684d9e49da6f1447458e43a8152ba6ae6774b957284Paul Westbrook if (values.containsKey(DELETED_COLUMN)) { 685f74147ff72429681d488a75166ec42d434456456mindyp // Item is deleted locally AND deleted in the new cursor. 686f74147ff72429681d488a75166ec42d434456456mindyp if (!newCursorWrapper.contains(key)) { 687f74147ff72429681d488a75166ec42d434456456mindyp // Keep the deleted count up-to-date; remove the 688f74147ff72429681d488a75166ec42d434456456mindyp // cache entry 689f74147ff72429681d488a75166ec42d434456456mindyp mDeletedCount--; 690f74147ff72429681d488a75166ec42d434456456mindyp removed = true; 691f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang LogUtils.i(LOG_TAG, 692f74147ff72429681d488a75166ec42d434456456mindyp "IN resetCursor, sDeletedCount decremented to: %d by %s", 693f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang mDeletedCount, 694f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) ? key 695f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang : "[redacted]"); 696f74147ff72429681d488a75166ec42d434456456mindyp } 697d9e49da6f1447458e43a8152ba6ae6774b957284Paul Westbrook } 698d9e49da6f1447458e43a8152ba6ae6774b957284Paul Westbrook } else { 6995c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LogUtils.e(LOG_TAG, "null ContentValues from mCacheMap for key: %s", key); 7007f55c685376659550ed11b047a78cd8d70158ad9mindyp } 701f74147ff72429681d488a75166ec42d434456456mindyp // Remove the entry if it was time for an update or the item was deleted by the user. 702f74147ff72429681d488a75166ec42d434456456mindyp if (!withinTimeWindow || removed) { 703f74147ff72429681d488a75166ec42d434456456mindyp iter.remove(); 704f74147ff72429681d488a75166ec42d434456456mindyp } 70548eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 70648eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 70728cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank // Swap cursor 70828cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank if (mUnderlyingCursor != null) { 70928cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank close(); 71028cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank } 7110a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook mUnderlyingCursor = newCursorWrapper; 71248eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 71328cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank mPosition = -1; 71428cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank mUnderlyingCursor.moveToPosition(mPosition); 71528cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank if (!mCursorObserverRegistered) { 71628cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank mUnderlyingCursor.registerContentObserver(mCursorObserver); 71728cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank mCursorObserverRegistered = true; 7182ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook 71928cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank } 72028cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank mRefreshRequired = false; 7212ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook 7222ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook // If the underlying cursor has received an update before we have gotten to this 7232ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook // point, we will want to make sure to refresh 7242ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook final boolean underlyingCursorUpdated = mUnderlyingCursor.isDataUpdated(); 7252ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook mUnderlyingCursor.disableUpdateNotifications(); 7262ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook if (underlyingCursorUpdated) { 7272ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook underlyingChanged(); 7282ed31f4c45316b65af0e4db02e76366b58d66768Paul Westbrook } 72997bca7b52aa2840494753d700a641161099cde23Marc Blank } 730c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang if (DEBUG) LogUtils.i(LOG_TAG, "OUT resetCursor, this=%s", this); 731c8a994227b9c686d88ee05840544162711a85712Marc Blank } 732c8a994227b9c686d88ee05840544162711a85712Marc Blank 733c8a994227b9c686d88ee05840544162711a85712Marc Blank /** 734fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook * Returns the conversation uris for the Conversations that the ConversationCursor is treating 735fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook * as deleted. This is an optimization to allow clients to determine if an item has been 736fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook * removed, without having to iterate through the whole cursor 737fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook */ 738fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook public Set<String> getDeletedItems() { 739fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook synchronized (mCacheMapLock) { 740fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook // Walk through the cache and return the list of uris that have been deleted 741fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook final Set<String> deletedItems = Sets.newHashSet(); 742a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal final Iterator<Map.Entry<String, ContentValues>> iter = 743fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook mCacheMap.entrySet().iterator(); 7449cf625efcbc9c7eccce8101e12c9e2d33d52672bAndy Huang final StringBuilder uriBuilder = new StringBuilder(); 745fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook while (iter.hasNext()) { 746a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal final Map.Entry<String, ContentValues> entry = iter.next(); 747fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook final ContentValues values = entry.getValue(); 748fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook if (values.containsKey(DELETED_COLUMN)) { 749fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook // Since clients of the conversation cursor see conversation ConversationCursor 750fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook // provider uris, we need to make sure that this also returns these uris 7519cf625efcbc9c7eccce8101e12c9e2d33d52672bAndy Huang deletedItems.add(uriToCachingUriString(entry.getKey(), uriBuilder)); 752fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook } 753fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook } 754fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook return deletedItems; 755fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook } 756fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook } 757fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook 758fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook /** 759e5ca52d91ee8482a81abbd80d625ef592db6fa8dYu Ping Hu * Returns the position of a conversation in the underlying cursor, without adjusting for the 760e5ca52d91ee8482a81abbd80d625ef592db6fa8dYu Ping Hu * cache. Notably, conversations which are marked as deleted in the cache but which haven't yet 761e5ca52d91ee8482a81abbd80d625ef592db6fa8dYu Ping Hu * been deleted in the underlying cursor will return non-negative here. 762e5ca52d91ee8482a81abbd80d625ef592db6fa8dYu Ping Hu * @param conversationId The id of the conversation we are looking for. 763e5ca52d91ee8482a81abbd80d625ef592db6fa8dYu Ping Hu * @return The position of the conversation in the underlying cursor, or -1 if not there. 764e5ca52d91ee8482a81abbd80d625ef592db6fa8dYu Ping Hu */ 765e5ca52d91ee8482a81abbd80d625ef592db6fa8dYu Ping Hu public int getUnderlyingPosition(final long conversationId) { 766e5ca52d91ee8482a81abbd80d625ef592db6fa8dYu Ping Hu return mUnderlyingCursor.getPosition(conversationId); 767e5ca52d91ee8482a81abbd80d625ef592db6fa8dYu Ping Hu } 768e5ca52d91ee8482a81abbd80d625ef592db6fa8dYu Ping Hu 769e5ca52d91ee8482a81abbd80d625ef592db6fa8dYu Ping Hu /** 770c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook * Returns the position, in the ConversationCursor, of the Conversation with the specified id. 771e5ca52d91ee8482a81abbd80d625ef592db6fa8dYu Ping Hu * The returned position will take into account any items that have been deleted. 772c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook */ 773c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook public int getConversationPosition(long conversationId) { 774c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook final int underlyingPosition = mUnderlyingCursor.getPosition(conversationId); 775c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook if (underlyingPosition < 0) { 776c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook // The conversation wasn't found in the underlying cursor, return the underlying result. 777c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook return underlyingPosition; 778c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook } 779c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook 780c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook // Walk through each of the deleted items. If the deleted item is before the underlying 781c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook // position, decrement the position 782c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook synchronized (mCacheMapLock) { 783c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook int updatedPosition = underlyingPosition; 784a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal final Iterator<Map.Entry<String, ContentValues>> iter = 785c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook mCacheMap.entrySet().iterator(); 786c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook while (iter.hasNext()) { 787a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal final Map.Entry<String, ContentValues> entry = iter.next(); 788c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook final ContentValues values = entry.getValue(); 789c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook if (values.containsKey(DELETED_COLUMN)) { 790c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook // Since clients of the conversation cursor see conversation ConversationCursor 791c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook // provider uris, we need to make sure that this also returns these uris 792c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook final String conversationUri = entry.getKey(); 793c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook final int deletedItemPosition = mUnderlyingCursor.getPosition(conversationUri); 794c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook if (deletedItemPosition == underlyingPosition) { 795c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook // The requested items has been deleted. 796c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook return -1; 797c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook } 798c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook 799c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook if (deletedItemPosition >= 0 && deletedItemPosition < underlyingPosition) { 800c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook // This item has been deleted, but is still in the underlying cursor, at 801c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook // a position before the requested item. Decrement the position of the 802c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook // requested item. 803c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook updatedPosition--; 804c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook } 805c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook } 806c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook } 807c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook return updatedPosition; 808c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook } 809c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook } 810c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook 811c8f2a3c6755b3b78af810aa73ab78487567cf074Paul Westbrook /** 812bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank * Add a listener for this cursor; we'll notify it when our data changes 813bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank */ 814bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank public void addListener(ConversationListener listener) { 8151892261a7dfa66b1c967ede0842a0cb2802f4187Paul Westbrook final int numPrevListeners; 816bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook synchronized (mListeners) { 8171892261a7dfa66b1c967ede0842a0cb2802f4187Paul Westbrook numPrevListeners = mListeners.size(); 818bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook if (!mListeners.contains(listener)) { 819bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mListeners.add(listener); 820bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank } else { 821d6020119c71ef9fe38725d0f0291cac7b9df6e79Vikram Aggarwal LogUtils.d(LOG_TAG, "Ignoring duplicate add of listener"); 822bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank } 823bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank } 8241892261a7dfa66b1c967ede0842a0cb2802f4187Paul Westbrook 8251892261a7dfa66b1c967ede0842a0cb2802f4187Paul Westbrook if (numPrevListeners == 0 && mRefreshRequired) { 8261892261a7dfa66b1c967ede0842a0cb2802f4187Paul Westbrook // A refresh is required, but it came when there were no listeners. Since this is the 8271892261a7dfa66b1c967ede0842a0cb2802f4187Paul Westbrook // first registered listener, we want to make sure that we don't drop this event. 8281892261a7dfa66b1c967ede0842a0cb2802f4187Paul Westbrook notifyRefreshRequired(); 8291892261a7dfa66b1c967ede0842a0cb2802f4187Paul Westbrook } 830bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank } 831bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank 832bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank /** 833bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank * Remove a listener for this cursor 834bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank */ 835bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank public void removeListener(ConversationListener listener) { 836bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook synchronized(mListeners) { 837bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mListeners.remove(listener); 838bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank } 839bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank } 840bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank 841144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang @Override 842144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang public void onStateChanged(DrawIdler idler, int newState) { 843144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang if (mUnderlyingCursor != null) { 844144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang mUnderlyingCursor.onStateChanged(idler, newState); 845144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang } 846144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang } 847144bfe739b93afdee0a1700a34806b0b287e5887Andy Huang 848bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank /** 849c8a994227b9c686d88ee05840544162711a85712Marc Blank * Generate a forwarding Uri to ConversationProvider from an original Uri. We do this by 850c8a994227b9c686d88ee05840544162711a85712Marc Blank * changing the authority to ours, but otherwise leaving the Uri intact. 851c8a994227b9c686d88ee05840544162711a85712Marc Blank * NOTE: This won't handle query parameters, so the functionality will need to be added if 852c8a994227b9c686d88ee05840544162711a85712Marc Blank * parameters are used in the future 853ba73fdddc4e85ef810a7b8d3aa6449656c44a317Alice Yang * @param uriStr the uri 854c8a994227b9c686d88ee05840544162711a85712Marc Blank * @return a forwarding uri to ConversationProvider 855c8a994227b9c686d88ee05840544162711a85712Marc Blank */ 8569cf625efcbc9c7eccce8101e12c9e2d33d52672bAndy Huang private static String uriToCachingUriString(String uriStr, StringBuilder sb) { 8579cf625efcbc9c7eccce8101e12c9e2d33d52672bAndy Huang final String withoutScheme = uriStr.substring( 8583663453628ab545bcba3327d972eecac72b6c2d0Andy Huang uriStr.indexOf(ConversationProvider.URI_SEPARATOR) 8593663453628ab545bcba3327d972eecac72b6c2d0Andy Huang + ConversationProvider.URI_SEPARATOR.length()); 8609cf625efcbc9c7eccce8101e12c9e2d33d52672bAndy Huang final String result; 8619cf625efcbc9c7eccce8101e12c9e2d33d52672bAndy Huang if (sb != null) { 8629cf625efcbc9c7eccce8101e12c9e2d33d52672bAndy Huang sb.setLength(0); 8639cf625efcbc9c7eccce8101e12c9e2d33d52672bAndy Huang sb.append(ConversationProvider.sUriPrefix); 8649cf625efcbc9c7eccce8101e12c9e2d33d52672bAndy Huang sb.append(withoutScheme); 8659cf625efcbc9c7eccce8101e12c9e2d33d52672bAndy Huang result = sb.toString(); 8669cf625efcbc9c7eccce8101e12c9e2d33d52672bAndy Huang } else { 8679cf625efcbc9c7eccce8101e12c9e2d33d52672bAndy Huang result = ConversationProvider.sUriPrefix + withoutScheme; 8689cf625efcbc9c7eccce8101e12c9e2d33d52672bAndy Huang } 8699cf625efcbc9c7eccce8101e12c9e2d33d52672bAndy Huang return result; 870c8a994227b9c686d88ee05840544162711a85712Marc Blank } 871c8a994227b9c686d88ee05840544162711a85712Marc Blank 872c8a994227b9c686d88ee05840544162711a85712Marc Blank /** 873c8a994227b9c686d88ee05840544162711a85712Marc Blank * Regenerate the original Uri from a forwarding (ConversationProvider) Uri 874c8a994227b9c686d88ee05840544162711a85712Marc Blank * NOTE: See note above for uriToCachingUri 875c8a994227b9c686d88ee05840544162711a85712Marc Blank * @param uri the forwarding Uri 876c8a994227b9c686d88ee05840544162711a85712Marc Blank * @return the original Uri 877c8a994227b9c686d88ee05840544162711a85712Marc Blank */ 878c8a994227b9c686d88ee05840544162711a85712Marc Blank private static Uri uriFromCachingUri(Uri uri) { 879d9787153f5000b629469951afd95438e2b64b66aMarc Blank String authority = uri.getAuthority(); 880d9787153f5000b629469951afd95438e2b64b66aMarc Blank // Don't modify uri's that aren't ours 881d9787153f5000b629469951afd95438e2b64b66aMarc Blank if (!authority.equals(ConversationProvider.AUTHORITY)) { 882d9787153f5000b629469951afd95438e2b64b66aMarc Blank return uri; 883d9787153f5000b629469951afd95438e2b64b66aMarc Blank } 884c8a994227b9c686d88ee05840544162711a85712Marc Blank List<String> path = uri.getPathSegments(); 885c8a994227b9c686d88ee05840544162711a85712Marc Blank Uri.Builder builder = new Uri.Builder().scheme(uri.getScheme()).authority(path.get(0)); 886c8a994227b9c686d88ee05840544162711a85712Marc Blank for (int i = 1; i < path.size(); i++) { 887c8a994227b9c686d88ee05840544162711a85712Marc Blank builder.appendPath(path.get(i)); 888c8a994227b9c686d88ee05840544162711a85712Marc Blank } 889c8a994227b9c686d88ee05840544162711a85712Marc Blank return builder.build(); 890c8a994227b9c686d88ee05840544162711a85712Marc Blank } 891c8a994227b9c686d88ee05840544162711a85712Marc Blank 892e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank private static String uriStringFromCachingUri(Uri uri) { 893e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank Uri underlyingUri = uriFromCachingUri(uri); 894e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank // Remember to decode the underlying Uri as it might be encoded (as w/ Gmail) 895e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank return Uri.decode(underlyingUri.toString()); 896e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 897e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank 898daa06aba887e8e3748453f45ae52c1e571fe9f2eAndy Huang public void setConversationColumn(Uri conversationUri, String columnName, Object value) { 899daa06aba887e8e3748453f45ae52c1e571fe9f2eAndy Huang final String uriStr = uriStringFromCachingUri(conversationUri); 900bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook synchronized (mCacheMapLock) { 901daa06aba887e8e3748453f45ae52c1e571fe9f2eAndy Huang cacheValue(uriStr, columnName, value); 902bec5115726f24733a0a1577caaf05fb6e9ef9c6fMarc Blank } 903bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook notifyDataChanged(); 904958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank } 905958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank 906958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank /** 907c8a994227b9c686d88ee05840544162711a85712Marc Blank * Cache a column name/value pair for a given Uri 908c8a994227b9c686d88ee05840544162711a85712Marc Blank * @param uriString the Uri for which the column name/value pair applies 909c8a994227b9c686d88ee05840544162711a85712Marc Blank * @param columnName the column name 910c8a994227b9c686d88ee05840544162711a85712Marc Blank * @param value the value to be cached 911c8a994227b9c686d88ee05840544162711a85712Marc Blank */ 912bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook private void cacheValue(String uriString, String columnName, Object value) { 913489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang // Calling this method off the UI thread will mess with ListView's reading of the cursor's 914489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang // count 915489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang if (offUiThread()) { 9165c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LogUtils.e(LOG_TAG, new Error(), 9175c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp "cacheValue incorrectly being called from non-UI thread"); 918489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang } 919489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang 920bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook synchronized (mCacheMapLock) { 921958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank // Get the map for our uri 922bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook ContentValues map = mCacheMap.get(uriString); 923958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank // Create one if necessary 924958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank if (map == null) { 925958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank map = new ContentValues(); 926bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mCacheMap.put(uriString, map); 927958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank } 928958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank // If we're caching a deletion, add to our count 929ec5b82ac3fc8f914fa1efdeff36ea3dc4e55b3e3Tony Mantler if (columnName.equals(DELETED_COLUMN)) { 930958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank final boolean state = (Boolean)value; 931958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank final boolean hasValue = map.get(columnName) != null; 932958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank if (state && !hasValue) { 933bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mDeletedCount++; 934958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank if (DEBUG) { 9355c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LogUtils.i(LOG_TAG, "Deleted %s, incremented deleted count=%d", uriString, 936bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mDeletedCount); 9372596f0011dcb4e86b01fc8a6362b5ac922239571Marc Blank } 938958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank } else if (!state && hasValue) { 939bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mDeletedCount--; 940958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank map.remove(columnName); 941958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank if (DEBUG) { 9425c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LogUtils.i(LOG_TAG, "Undeleted %s, decremented deleted count=%d", uriString, 943bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mDeletedCount); 9442596f0011dcb4e86b01fc8a6362b5ac922239571Marc Blank } 945958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank return; 946958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank } else if (!state) { 947958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank // Trying to undelete, but it's not deleted; just return 948958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank if (DEBUG) { 9495c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LogUtils.i(LOG_TAG, "Undeleted %s, IGNORING, deleted count=%d", uriString, 950bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mDeletedCount); 951958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank } 952958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank return; 95348eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 95448eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 9551bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang putInValues(map, columnName, value); 9567f55c685376659550ed11b047a78cd8d70158ad9mindyp map.put(UPDATE_TIME_COLUMN, System.currentTimeMillis()); 957ec5b82ac3fc8f914fa1efdeff36ea3dc4e55b3e3Tony Mantler if (DEBUG && (!columnName.equals(DELETED_COLUMN))) { 9585c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LogUtils.i(LOG_TAG, "Caching value for %s: %s", uriString, columnName); 959958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank } 960c8a994227b9c686d88ee05840544162711a85712Marc Blank } 961c8a994227b9c686d88ee05840544162711a85712Marc Blank } 962c8a994227b9c686d88ee05840544162711a85712Marc Blank 963c8a994227b9c686d88ee05840544162711a85712Marc Blank /** 964c8a994227b9c686d88ee05840544162711a85712Marc Blank * Get the cached value for the provided column; we special case -1 as the "deleted" column 965c8a994227b9c686d88ee05840544162711a85712Marc Blank * @param columnIndex the index of the column whose cached value we want to retrieve 966c8a994227b9c686d88ee05840544162711a85712Marc Blank * @return the cached value for this column, or null if there is none 967c8a994227b9c686d88ee05840544162711a85712Marc Blank */ 968c8a994227b9c686d88ee05840544162711a85712Marc Blank private Object getCachedValue(int columnIndex) { 9691bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang final String uri = mUnderlyingCursor.getInnerUri(); 970e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank return getCachedValue(uri, columnIndex); 971e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 972e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank 973e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank private Object getCachedValue(String uri, int columnIndex) { 974bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook ContentValues uriMap = mCacheMap.get(uri); 975c8a994227b9c686d88ee05840544162711a85712Marc Blank if (uriMap != null) { 976c8a994227b9c686d88ee05840544162711a85712Marc Blank String columnName; 977c8a994227b9c686d88ee05840544162711a85712Marc Blank if (columnIndex == DELETED_COLUMN_INDEX) { 978c8a994227b9c686d88ee05840544162711a85712Marc Blank columnName = DELETED_COLUMN; 979c8a994227b9c686d88ee05840544162711a85712Marc Blank } else { 980c8a994227b9c686d88ee05840544162711a85712Marc Blank columnName = mColumnNames[columnIndex]; 981c8a994227b9c686d88ee05840544162711a85712Marc Blank } 982c8a994227b9c686d88ee05840544162711a85712Marc Blank return uriMap.get(columnName); 983c8a994227b9c686d88ee05840544162711a85712Marc Blank } 984c8a994227b9c686d88ee05840544162711a85712Marc Blank return null; 985c8a994227b9c686d88ee05840544162711a85712Marc Blank } 986c8a994227b9c686d88ee05840544162711a85712Marc Blank 987c8a994227b9c686d88ee05840544162711a85712Marc Blank /** 98897bca7b52aa2840494753d700a641161099cde23Marc Blank * When the underlying cursor changes, we want to alert the listener 989c8a994227b9c686d88ee05840544162711a85712Marc Blank */ 990c8a994227b9c686d88ee05840544162711a85712Marc Blank private void underlyingChanged() { 991c33ec4ce932c94c42a2813c666009de2f50d71fbPaul Westbrook synchronized(mCacheMapLock) { 99228cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank if (mCursorObserverRegistered) { 99328cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank try { 99428cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank mUnderlyingCursor.unregisterContentObserver(mCursorObserver); 99528cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank } catch (IllegalStateException e) { 99628cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank // Maybe the cursor was GC'd? 99728cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank } 99828cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank mCursorObserverRegistered = false; 99928cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank } 1000bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mRefreshRequired = true; 1001c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang if (DEBUG) LogUtils.i(LOG_TAG, "IN underlyingChanged, this=%s", this); 1002bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook if (!mPaused) { 1003e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank notifyRefreshRequired(); 1004f9d8719ff6378e44d41b7586756ea9b6efac8fdfMarc Blank } 1005c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang if (DEBUG) LogUtils.i(LOG_TAG, "OUT underlyingChanged, this=%s", this); 1006b600a83dfe6d0ec0b342de9b11f3b185b048ab93Marc Blank } 1007e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 1008e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank 1009e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank /** 1010e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank * Must be called on UI thread; notify listeners that a refresh is required 1011e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank */ 1012e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank private void notifyRefreshRequired() { 1013c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang if (DEBUG) LogUtils.i(LOG_TAG, "[Notify: onRefreshRequired() this=%s]", this); 1014bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook if (!mDeferSync) { 1015bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook synchronized(mListeners) { 1016bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook for (ConversationListener listener: mListeners) { 1017bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank listener.onRefreshRequired(); 1018bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank } 1019bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank } 1020e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 1021e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 1022e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank 1023e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank /** 1024e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank * Must be called on UI thread; notify listeners that a new cursor is ready 1025e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank */ 1026e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank private void notifyRefreshReady() { 1027e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank if (DEBUG) { 10285c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LogUtils.i(LOG_TAG, "[Notify %s: onRefreshReady(), %d listeners]", 1029e2bde3a97c57b18a84047be03a042e928921a7aePaul Westbrook mName, mListeners.size()); 1030e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 1031bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook synchronized(mListeners) { 1032bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook for (ConversationListener listener: mListeners) { 1033bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank listener.onRefreshReady(); 1034bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank } 1035bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank } 1036e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 1037e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank 1038e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank /** 1039e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank * Must be called on UI thread; notify listeners that data has changed 1040e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank */ 1041e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank private void notifyDataChanged() { 1042e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank if (DEBUG) { 10435c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LogUtils.i(LOG_TAG, "[Notify %s: onDataSetChanged()]", mName); 1044e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 1045bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook synchronized(mListeners) { 1046bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook for (ConversationListener listener: mListeners) { 1047bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank listener.onDataSetChanged(); 1048bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank } 1049bf128ebfb9aa53d28118e7143d5674e506fedfefMarc Blank } 105008a079c3d2857e365736432b2691187767eb116fScott Kennedy 105108a079c3d2857e365736432b2691187767eb116fScott Kennedy handleNotificationActions(); 1052c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1053c8a994227b9c686d88ee05840544162711a85712Marc Blank 10544015c182ab04edaa7cd8b75490f4336348ec29daMarc Blank /** 10554015c182ab04edaa7cd8b75490f4336348ec29daMarc Blank * Put the refreshed cursor in place (called by the UI) 10564015c182ab04edaa7cd8b75490f4336348ec29daMarc Blank */ 10574e25c949d04e42ec6bf84baef52e99c13e83ac9fMarc Blank public void sync() { 105828cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank if (mRequeryCursor == null) { 105909b32383b951afe1dee7845f062fcf8050601f61Marc Blank // This can happen during an animated deletion, if the UI isn't keeping track, or 106009b32383b951afe1dee7845f062fcf8050601f61Marc Blank // if a new query intervened (i.e. user changed folders) 1061948985b753588aeda2e83aa71eeb738c19963820Marc Blank if (DEBUG) { 10625c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LogUtils.i(LOG_TAG, "[sync() %s; no requery cursor]", mName); 1063948985b753588aeda2e83aa71eeb738c19963820Marc Blank } 106409b32383b951afe1dee7845f062fcf8050601f61Marc Blank return; 106509b32383b951afe1dee7845f062fcf8050601f61Marc Blank } 1066bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook synchronized(mCacheMapLock) { 106728cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank if (DEBUG) { 10685c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LogUtils.i(LOG_TAG, "[sync() %s]", mName); 106928cb53d729459d0835e435efe5dae9cd424b1ad1Marc Blank } 1070bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mRefreshTask = null; 1071bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mRefreshReady = false; 1072a0214391331f3248569b74788639b491732e6427Andy Huang resetCursor(mRequeryCursor); 1073a0214391331f3248569b74788639b491732e6427Andy Huang mRequeryCursor = null; 107448eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 107566150d75d09324e867be42ff8c0ea8fae115ef20Paul Westbrook notifyDataChanged(); 10764e25c949d04e42ec6bf84baef52e99c13e83ac9fMarc Blank } 10774e25c949d04e42ec6bf84baef52e99c13e83ac9fMarc Blank 10784e25c949d04e42ec6bf84baef52e99c13e83ac9fMarc Blank public boolean isRefreshRequired() { 1079bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return mRefreshRequired; 10804e25c949d04e42ec6bf84baef52e99c13e83ac9fMarc Blank } 10814e25c949d04e42ec6bf84baef52e99c13e83ac9fMarc Blank 10824e25c949d04e42ec6bf84baef52e99c13e83ac9fMarc Blank public boolean isRefreshReady() { 1083bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return mRefreshReady; 10844015c182ab04edaa7cd8b75490f4336348ec29daMarc Blank } 10854015c182ab04edaa7cd8b75490f4336348ec29daMarc Blank 10864015c182ab04edaa7cd8b75490f4336348ec29daMarc Blank /** 108748eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank * When we get a requery from the UI, we'll do it, but also clear the cache. The listener is 108848eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank * notified when the requery is complete 108997bca7b52aa2840494753d700a641161099cde23Marc Blank * NOTE: This will have to change, of course, when we start using loaders... 109097bca7b52aa2840494753d700a641161099cde23Marc Blank */ 109148eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public boolean refresh() { 1092c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang if (DEBUG) LogUtils.i(LOG_TAG, "[refresh() this=%s]", this); 1093bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook synchronized(mCacheMapLock) { 1094bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook if (mRefreshTask != null) { 1095e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank if (DEBUG) { 10965c1d8354ce98be2fc77d458bcc5c3346765046a0mindyp LogUtils.i(LOG_TAG, "[refresh() %s returning; already running %d]", 1097e2bde3a97c57b18a84047be03a042e928921a7aePaul Westbrook mName, mRefreshTask.hashCode()); 10984015c182ab04edaa7cd8b75490f4336348ec29daMarc Blank } 1099e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank return false; 110048eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 1101c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang if (mUnderlyingCursor != null) { 1102c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang mUnderlyingCursor.stopCaching(); 1103c4e230fd260e8d80cffc0515bb9322f7553853f9Andy Huang } 1104bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mRefreshTask = new RefreshTask(); 1105bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mRefreshTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 1106e3d36791e0e80a0d7065a26e727aedb70863c45aMarc Blank } 1107c8a994227b9c686d88ee05840544162711a85712Marc Blank return true; 1108c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1109c8a994227b9c686d88ee05840544162711a85712Marc Blank 1110bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook public void disable() { 1111bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook close(); 1112bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mCacheMap.clear(); 1113bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mListeners.clear(); 1114bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mUnderlyingCursor = null; 1115bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 1116bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 1117b600a83dfe6d0ec0b342de9b11f3b185b048ab93Marc Blank @Override 1118c8a994227b9c686d88ee05840544162711a85712Marc Blank public void close() { 1119bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook if (mUnderlyingCursor != null && !mUnderlyingCursor.isClosed()) { 1120f9d8719ff6378e44d41b7586756ea9b6efac8fdfMarc Blank // Unregister our observer on the underlying cursor and close as usual 1121f9d8719ff6378e44d41b7586756ea9b6efac8fdfMarc Blank if (mCursorObserverRegistered) { 1122f9d8719ff6378e44d41b7586756ea9b6efac8fdfMarc Blank try { 1123bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mUnderlyingCursor.unregisterContentObserver(mCursorObserver); 1124f9d8719ff6378e44d41b7586756ea9b6efac8fdfMarc Blank } catch (IllegalStateException e) { 1125f9d8719ff6378e44d41b7586756ea9b6efac8fdfMarc Blank // Maybe the cursor got GC'd? 1126f9d8719ff6378e44d41b7586756ea9b6efac8fdfMarc Blank } 1127f9d8719ff6378e44d41b7586756ea9b6efac8fdfMarc Blank mCursorObserverRegistered = false; 1128f9d8719ff6378e44d41b7586756ea9b6efac8fdfMarc Blank } 1129bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mUnderlyingCursor.close(); 1130dd10bc8736282262da0cd9a5f9a0236c10b47028Marc Blank } 1131c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1132c8a994227b9c686d88ee05840544162711a85712Marc Blank 1133c8a994227b9c686d88ee05840544162711a85712Marc Blank /** 1134c8a994227b9c686d88ee05840544162711a85712Marc Blank * Move to the next not-deleted item in the conversation 1135c8a994227b9c686d88ee05840544162711a85712Marc Blank */ 1136b600a83dfe6d0ec0b342de9b11f3b185b048ab93Marc Blank @Override 1137c8a994227b9c686d88ee05840544162711a85712Marc Blank public boolean moveToNext() { 1138c8a994227b9c686d88ee05840544162711a85712Marc Blank while (true) { 1139bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook boolean ret = mUnderlyingCursor.moveToNext(); 11405d8b1fbb708eaa0e1b38e67e0485d31a40b936ccMarc Blank if (!ret) { 114144d5f0af86423462e80b845fbe00b4633321acc0Marc Blank mPosition = getCount(); 1142d6020119c71ef9fe38725d0f0291cac7b9df6e79Vikram Aggarwal if (DEBUG) { 1143d6020119c71ef9fe38725d0f0291cac7b9df6e79Vikram Aggarwal LogUtils.i(LOG_TAG, "*** moveToNext returns false: pos = %d, und = %d" + 1144d6020119c71ef9fe38725d0f0291cac7b9df6e79Vikram Aggarwal ", del = %d", mPosition, mUnderlyingCursor.getPosition(), 1145d6020119c71ef9fe38725d0f0291cac7b9df6e79Vikram Aggarwal mDeletedCount); 1146d6020119c71ef9fe38725d0f0291cac7b9df6e79Vikram Aggarwal } 11475d8b1fbb708eaa0e1b38e67e0485d31a40b936ccMarc Blank return false; 11485d8b1fbb708eaa0e1b38e67e0485d31a40b936ccMarc Blank } 1149c8a994227b9c686d88ee05840544162711a85712Marc Blank if (getCachedValue(DELETED_COLUMN_INDEX) instanceof Integer) continue; 1150c8a994227b9c686d88ee05840544162711a85712Marc Blank mPosition++; 1151c8a994227b9c686d88ee05840544162711a85712Marc Blank return true; 1152c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1153c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1154c8a994227b9c686d88ee05840544162711a85712Marc Blank 1155c8a994227b9c686d88ee05840544162711a85712Marc Blank /** 1156c8a994227b9c686d88ee05840544162711a85712Marc Blank * Move to the previous not-deleted item in the conversation 1157c8a994227b9c686d88ee05840544162711a85712Marc Blank */ 1158b600a83dfe6d0ec0b342de9b11f3b185b048ab93Marc Blank @Override 1159c8a994227b9c686d88ee05840544162711a85712Marc Blank public boolean moveToPrevious() { 1160c8a994227b9c686d88ee05840544162711a85712Marc Blank while (true) { 1161bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook boolean ret = mUnderlyingCursor.moveToPrevious(); 11625d8b1fbb708eaa0e1b38e67e0485d31a40b936ccMarc Blank if (!ret) { 11635d8b1fbb708eaa0e1b38e67e0485d31a40b936ccMarc Blank // Make sure we're before the first position 11645d8b1fbb708eaa0e1b38e67e0485d31a40b936ccMarc Blank mPosition = -1; 11655d8b1fbb708eaa0e1b38e67e0485d31a40b936ccMarc Blank return false; 11665d8b1fbb708eaa0e1b38e67e0485d31a40b936ccMarc Blank } 1167ec7c4da49c023554fb57326f1a8eafa9a53760dcMarc Blank if (getCachedValue(DELETED_COLUMN_INDEX) instanceof Integer) continue; 1168c8a994227b9c686d88ee05840544162711a85712Marc Blank mPosition--; 1169c8a994227b9c686d88ee05840544162711a85712Marc Blank return true; 1170c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1171c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1172c8a994227b9c686d88ee05840544162711a85712Marc Blank 1173b600a83dfe6d0ec0b342de9b11f3b185b048ab93Marc Blank @Override 1174c8a994227b9c686d88ee05840544162711a85712Marc Blank public int getPosition() { 1175c8a994227b9c686d88ee05840544162711a85712Marc Blank return mPosition; 1176c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1177c8a994227b9c686d88ee05840544162711a85712Marc Blank 1178c8a994227b9c686d88ee05840544162711a85712Marc Blank /** 1179c8a994227b9c686d88ee05840544162711a85712Marc Blank * The actual cursor's count must be decremented by the number we've deleted from the UI 1180c8a994227b9c686d88ee05840544162711a85712Marc Blank */ 1181b600a83dfe6d0ec0b342de9b11f3b185b048ab93Marc Blank @Override 1182c8a994227b9c686d88ee05840544162711a85712Marc Blank public int getCount() { 1183bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook if (mUnderlyingCursor == null) { 1184bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook throw new IllegalStateException( 1185bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook "getCount() on disabled cursor: " + mName + "(" + qUri + ")"); 1186bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 1187bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return mUnderlyingCursor.getCount() - mDeletedCount; 1188c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1189c8a994227b9c686d88ee05840544162711a85712Marc Blank 1190b600a83dfe6d0ec0b342de9b11f3b185b048ab93Marc Blank @Override 1191c8a994227b9c686d88ee05840544162711a85712Marc Blank public boolean moveToFirst() { 1192bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook if (mUnderlyingCursor == null) { 1193bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook throw new IllegalStateException( 1194bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook "moveToFirst() on disabled cursor: " + mName + "(" + qUri + ")"); 1195bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 1196bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mUnderlyingCursor.moveToPosition(-1); 1197c8a994227b9c686d88ee05840544162711a85712Marc Blank mPosition = -1; 1198c8a994227b9c686d88ee05840544162711a85712Marc Blank return moveToNext(); 1199c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1200c8a994227b9c686d88ee05840544162711a85712Marc Blank 1201b600a83dfe6d0ec0b342de9b11f3b185b048ab93Marc Blank @Override 1202c8a994227b9c686d88ee05840544162711a85712Marc Blank public boolean moveToPosition(int pos) { 1203bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook if (mUnderlyingCursor == null) { 1204bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook throw new IllegalStateException( 1205bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook "moveToPosition() on disabled cursor: " + mName + "(" + qUri + ")"); 1206bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 12077ffbaaabf43e2d9d2b6c19b1a061d2c6bf907813Marc Blank // Handle the "move to first" case before anything else; moveToPosition(0) in an empty 12087ffbaaabf43e2d9d2b6c19b1a061d2c6bf907813Marc Blank // SQLiteCursor moves the position to 0 when returning false, which we will mirror. 12097ffbaaabf43e2d9d2b6c19b1a061d2c6bf907813Marc Blank // But we don't want to return true on a subsequent "move to first", which we would if we 12107ffbaaabf43e2d9d2b6c19b1a061d2c6bf907813Marc Blank // check pos vs mPosition first 121135f6bba5e600342fb47dc5bd7a90c02fe9e576demindyp if (mUnderlyingCursor.getPosition() == -1) { 1212d6020119c71ef9fe38725d0f0291cac7b9df6e79Vikram Aggarwal LogUtils.d(LOG_TAG, "*** Underlying cursor position is -1 asking to move from %d to %d", 121335f6bba5e600342fb47dc5bd7a90c02fe9e576demindyp mPosition, pos); 121435f6bba5e600342fb47dc5bd7a90c02fe9e576demindyp } 12157ffbaaabf43e2d9d2b6c19b1a061d2c6bf907813Marc Blank if (pos == 0) { 12167ffbaaabf43e2d9d2b6c19b1a061d2c6bf907813Marc Blank return moveToFirst(); 1217c84d251f5cc63ab398ffc6cecc06f801664d0368Paul Westbrook } else if (pos < 0) { 1218c84d251f5cc63ab398ffc6cecc06f801664d0368Paul Westbrook mPosition = -1; 1219c84d251f5cc63ab398ffc6cecc06f801664d0368Paul Westbrook mUnderlyingCursor.moveToPosition(mPosition); 1220c84d251f5cc63ab398ffc6cecc06f801664d0368Paul Westbrook return false; 12217ffbaaabf43e2d9d2b6c19b1a061d2c6bf907813Marc Blank } else if (pos == mPosition) { 12229735cdca3abbe813f0b9d8f13a4c586c9c0acd8eMarc Blank // Return false if we're past the end of the cursor 12239735cdca3abbe813f0b9d8f13a4c586c9c0acd8eMarc Blank return pos < getCount(); 12247ffbaaabf43e2d9d2b6c19b1a061d2c6bf907813Marc Blank } else if (pos > mPosition) { 1225c8a994227b9c686d88ee05840544162711a85712Marc Blank while (pos > mPosition) { 1226c8a994227b9c686d88ee05840544162711a85712Marc Blank if (!moveToNext()) { 1227c8a994227b9c686d88ee05840544162711a85712Marc Blank return false; 1228c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1229c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1230c8a994227b9c686d88ee05840544162711a85712Marc Blank return true; 123118a9717e1f93f55d5be8b75baa0e9353ddbb25a4Marc Blank } else if ((pos >= 0) && (mPosition - pos) > pos) { 1232e5884c70ddc68c55861d0c1b945346316b0ca428Marc Blank // Optimization if it's easier to move forward to position instead of backward 1233d6020119c71ef9fe38725d0f0291cac7b9df6e79Vikram Aggarwal if (DEBUG) { 1234d6020119c71ef9fe38725d0f0291cac7b9df6e79Vikram Aggarwal LogUtils.i(LOG_TAG, "*** Move from %d to %d, starting from first", mPosition, pos); 1235d6020119c71ef9fe38725d0f0291cac7b9df6e79Vikram Aggarwal } 1236e5884c70ddc68c55861d0c1b945346316b0ca428Marc Blank moveToFirst(); 1237e5884c70ddc68c55861d0c1b945346316b0ca428Marc Blank return moveToPosition(pos); 1238c8a994227b9c686d88ee05840544162711a85712Marc Blank } else { 1239c8a994227b9c686d88ee05840544162711a85712Marc Blank while (pos < mPosition) { 1240c8a994227b9c686d88ee05840544162711a85712Marc Blank if (!moveToPrevious()) { 1241c8a994227b9c686d88ee05840544162711a85712Marc Blank return false; 1242c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1243c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1244c8a994227b9c686d88ee05840544162711a85712Marc Blank return true; 1245c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1246c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1247c8a994227b9c686d88ee05840544162711a85712Marc Blank 124893b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank /** 124993b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank * Make sure mPosition is correct after locally deleting/undeleting items 125093b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank */ 125193b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank private void recalibratePosition() { 1252c84d251f5cc63ab398ffc6cecc06f801664d0368Paul Westbrook final int pos = mPosition; 125393b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank moveToFirst(); 125493b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank moveToPosition(pos); 125593b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank } 125693b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank 1257b600a83dfe6d0ec0b342de9b11f3b185b048ab93Marc Blank @Override 1258c8a994227b9c686d88ee05840544162711a85712Marc Blank public boolean moveToLast() { 1259c8a994227b9c686d88ee05840544162711a85712Marc Blank throw new UnsupportedOperationException("moveToLast unsupported!"); 1260c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1261c8a994227b9c686d88ee05840544162711a85712Marc Blank 1262b600a83dfe6d0ec0b342de9b11f3b185b048ab93Marc Blank @Override 1263c8a994227b9c686d88ee05840544162711a85712Marc Blank public boolean move(int offset) { 1264c8a994227b9c686d88ee05840544162711a85712Marc Blank throw new UnsupportedOperationException("move unsupported!"); 1265c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1266c8a994227b9c686d88ee05840544162711a85712Marc Blank 1267c8a994227b9c686d88ee05840544162711a85712Marc Blank /** 1268c8a994227b9c686d88ee05840544162711a85712Marc Blank * We need to override all of the getters to make sure they look at cached values before using 1269c8a994227b9c686d88ee05840544162711a85712Marc Blank * the values in the underlying cursor 1270c8a994227b9c686d88ee05840544162711a85712Marc Blank */ 1271c8a994227b9c686d88ee05840544162711a85712Marc Blank @Override 1272c8a994227b9c686d88ee05840544162711a85712Marc Blank public double getDouble(int columnIndex) { 1273c8a994227b9c686d88ee05840544162711a85712Marc Blank Object obj = getCachedValue(columnIndex); 1274c8a994227b9c686d88ee05840544162711a85712Marc Blank if (obj != null) return (Double)obj; 1275bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return mUnderlyingCursor.getDouble(columnIndex); 1276c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1277c8a994227b9c686d88ee05840544162711a85712Marc Blank 1278c8a994227b9c686d88ee05840544162711a85712Marc Blank @Override 1279c8a994227b9c686d88ee05840544162711a85712Marc Blank public float getFloat(int columnIndex) { 1280c8a994227b9c686d88ee05840544162711a85712Marc Blank Object obj = getCachedValue(columnIndex); 1281c8a994227b9c686d88ee05840544162711a85712Marc Blank if (obj != null) return (Float)obj; 1282bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return mUnderlyingCursor.getFloat(columnIndex); 1283c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1284c8a994227b9c686d88ee05840544162711a85712Marc Blank 1285c8a994227b9c686d88ee05840544162711a85712Marc Blank @Override 1286c8a994227b9c686d88ee05840544162711a85712Marc Blank public int getInt(int columnIndex) { 1287c8a994227b9c686d88ee05840544162711a85712Marc Blank Object obj = getCachedValue(columnIndex); 1288c8a994227b9c686d88ee05840544162711a85712Marc Blank if (obj != null) return (Integer)obj; 1289bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return mUnderlyingCursor.getInt(columnIndex); 1290c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1291c8a994227b9c686d88ee05840544162711a85712Marc Blank 1292c8a994227b9c686d88ee05840544162711a85712Marc Blank @Override 1293c8a994227b9c686d88ee05840544162711a85712Marc Blank public long getLong(int columnIndex) { 1294e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank Object obj = getCachedValue(columnIndex); 1295e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank if (obj != null) return (Long)obj; 1296bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return mUnderlyingCursor.getLong(columnIndex); 1297c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1298c8a994227b9c686d88ee05840544162711a85712Marc Blank 1299c8a994227b9c686d88ee05840544162711a85712Marc Blank @Override 1300c8a994227b9c686d88ee05840544162711a85712Marc Blank public short getShort(int columnIndex) { 1301c8a994227b9c686d88ee05840544162711a85712Marc Blank Object obj = getCachedValue(columnIndex); 1302c8a994227b9c686d88ee05840544162711a85712Marc Blank if (obj != null) return (Short)obj; 1303bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return mUnderlyingCursor.getShort(columnIndex); 1304c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1305c8a994227b9c686d88ee05840544162711a85712Marc Blank 1306c8a994227b9c686d88ee05840544162711a85712Marc Blank @Override 1307c8a994227b9c686d88ee05840544162711a85712Marc Blank public String getString(int columnIndex) { 1308c8a994227b9c686d88ee05840544162711a85712Marc Blank // If we're asking for the Uri for the conversation list, we return a forwarding URI 1309c8a994227b9c686d88ee05840544162711a85712Marc Blank // so that we can intercept update/delete and handle it ourselves 13108d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal if (columnIndex == URI_COLUMN_INDEX) { 131147cd4c3b064da3aac82e4ca1a057fd51c4ebad77Andy Huang return uriToCachingUriString(mUnderlyingCursor.getInnerUri(), null); 1312c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1313c8a994227b9c686d88ee05840544162711a85712Marc Blank Object obj = getCachedValue(columnIndex); 1314c8a994227b9c686d88ee05840544162711a85712Marc Blank if (obj != null) return (String)obj; 1315bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return mUnderlyingCursor.getString(columnIndex); 1316c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1317c8a994227b9c686d88ee05840544162711a85712Marc Blank 1318c8a994227b9c686d88ee05840544162711a85712Marc Blank @Override 1319c8a994227b9c686d88ee05840544162711a85712Marc Blank public byte[] getBlob(int columnIndex) { 1320c8a994227b9c686d88ee05840544162711a85712Marc Blank Object obj = getCachedValue(columnIndex); 1321c8a994227b9c686d88ee05840544162711a85712Marc Blank if (obj != null) return (byte[])obj; 1322bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return mUnderlyingCursor.getBlob(columnIndex); 1323c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1324c8a994227b9c686d88ee05840544162711a85712Marc Blank 1325f8b613c1103952ae5cc1f8a5e6ae60aa62d73decAndy Huang public byte[] getCachedBlob(int columnIndex) { 1326f8b613c1103952ae5cc1f8a5e6ae60aa62d73decAndy Huang return (byte[]) getCachedValue(columnIndex); 1327f8b613c1103952ae5cc1f8a5e6ae60aa62d73decAndy Huang } 1328f8b613c1103952ae5cc1f8a5e6ae60aa62d73decAndy Huang 13291bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang public Conversation getConversation() { 1330cef97ff7118b0a30cff842129300e0633d220043Andy Huang Conversation c = getCachedConversation(); 13311bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang if (c == null) { 13321bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang // not pre-cached. fall back to just-in-time construction. 13331bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang c = new Conversation(this); 13346de11b098c86f45a3c21dd59b548da91cb736892Paul Westbrook mUnderlyingCursor.cacheConversation(c); 13351bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang } 13361bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang 13371bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang return c; 13381bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang } 13391bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang 1340983a723931447b6261a2e42b25e6f931dba6de33Paul Westbrook /** 13416cf45c601317d4b65ffb1896760fa1cb8a2b807cAndy Huang * Returns a Conversation object for the current position, or null if it has not yet been 13426cf45c601317d4b65ffb1896760fa1cb8a2b807cAndy Huang * cached. 134355e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang * 1344cef97ff7118b0a30cff842129300e0633d220043Andy Huang * This method will apply any cached column data to the result. 1345cef97ff7118b0a30cff842129300e0633d220043Andy Huang * 134655e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang */ 13476cf45c601317d4b65ffb1896760fa1cb8a2b807cAndy Huang public Conversation getCachedConversation() { 1348cef97ff7118b0a30cff842129300e0633d220043Andy Huang Conversation result = mUnderlyingCursor.getConversation(); 1349cef97ff7118b0a30cff842129300e0633d220043Andy Huang if (result == null) { 1350cef97ff7118b0a30cff842129300e0633d220043Andy Huang return null; 1351cef97ff7118b0a30cff842129300e0633d220043Andy Huang } 135255e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang 135355e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang // apply any cached values 135455e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang // but skip over any cached values that aren't part of the cursor projection 1355cef97ff7118b0a30cff842129300e0633d220043Andy Huang final ContentValues values = mCacheMap.get(mUnderlyingCursor.getInnerUri()); 135655e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang if (values != null) { 135755e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang final ContentValues queryableValues = new ContentValues(); 135855e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang for (String key : values.keySet()) { 135955e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang if (!mColumnNameSet.contains(key)) { 136055e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang continue; 136155e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang } 136255e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang putInValues(queryableValues, key, values.get(key)); 136355e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang } 136455e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang if (queryableValues.size() > 0) { 136555e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang // copy-on-write to help ensure the underlying cached Conversation is immutable 136655e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang // of course, any callers this method should also try not to modify them 136755e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang // overmuch... 1368cef97ff7118b0a30cff842129300e0633d220043Andy Huang result = new Conversation(result); 136955e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang result.applyCachedValues(queryableValues); 137055e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang } 137155e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang } 137255e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang return result; 137355e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang } 137455e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang 137555e76949ca7464c3bd55fdc25fb3daa43466ef5cAndy Huang /** 1376983a723931447b6261a2e42b25e6f931dba6de33Paul Westbrook * Notifies the provider of the position of the conversation being accessed by the UI 1377983a723931447b6261a2e42b25e6f931dba6de33Paul Westbrook */ 1378983a723931447b6261a2e42b25e6f931dba6de33Paul Westbrook public void notifyUIPositionChange() { 1379983a723931447b6261a2e42b25e6f931dba6de33Paul Westbrook mUnderlyingCursor.notifyConversationUIPositionChange(); 1380983a723931447b6261a2e42b25e6f931dba6de33Paul Westbrook } 1381983a723931447b6261a2e42b25e6f931dba6de33Paul Westbrook 13821bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang private static void putInValues(ContentValues dest, String key, Object value) { 13831bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang // ContentValues has no generic "put", so we must test. For now, the only classes 13841bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang // of values implemented are Boolean/Integer/String/Blob, though others are trivially 13851bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang // added 13861bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang if (value instanceof Boolean) { 13871bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang dest.put(key, ((Boolean) value).booleanValue() ? 1 : 0); 13881bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang } else if (value instanceof Integer) { 13891bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang dest.put(key, (Integer) value); 13901bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang } else if (value instanceof String) { 13911bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang dest.put(key, (String) value); 13921bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang } else if (value instanceof byte[]) { 13931bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang dest.put(key, (byte[])value); 13941bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang } else { 13951bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang final String cname = value.getClass().getName(); 13961bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang throw new IllegalArgumentException("Value class not compatible with cache: " 13971bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang + cname); 13981bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang } 13991bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang } 14001bca265b7a8a3f9ea08e0ae51eeb145f0883a266Andy Huang 1401c8a994227b9c686d88ee05840544162711a85712Marc Blank /** 1402c8a994227b9c686d88ee05840544162711a85712Marc Blank * Observer of changes to underlying data 1403c8a994227b9c686d88ee05840544162711a85712Marc Blank */ 1404c8a994227b9c686d88ee05840544162711a85712Marc Blank private class CursorObserver extends ContentObserver { 1405e77d5262a98d28de198c06c6ef84b6b70047a60aMarc Blank public CursorObserver(Handler handler) { 1406e77d5262a98d28de198c06c6ef84b6b70047a60aMarc Blank super(handler); 1407c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1408c8a994227b9c686d88ee05840544162711a85712Marc Blank 1409c8a994227b9c686d88ee05840544162711a85712Marc Blank @Override 1410c8a994227b9c686d88ee05840544162711a85712Marc Blank public void onChange(boolean selfChange) { 1411c8a994227b9c686d88ee05840544162711a85712Marc Blank // If we're here, then something outside of the UI has changed the data, and we 1412e77d5262a98d28de198c06c6ef84b6b70047a60aMarc Blank // must query the underlying provider for that data; 1413c8a994227b9c686d88ee05840544162711a85712Marc Blank ConversationCursor.this.underlyingChanged(); 1414c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1415c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1416c8a994227b9c686d88ee05840544162711a85712Marc Blank 1417c8a994227b9c686d88ee05840544162711a85712Marc Blank /** 1418c8a994227b9c686d88ee05840544162711a85712Marc Blank * ConversationProvider is the ContentProvider for our forwarding Uri's; it passes queries 1419c8a994227b9c686d88ee05840544162711a85712Marc Blank * and inserts directly, and caches updates/deletes before passing them through. The caching 1420c8a994227b9c686d88ee05840544162711a85712Marc Blank * will cause a redraw of the list with updated values. 1421c8a994227b9c686d88ee05840544162711a85712Marc Blank */ 142277177b171c483d485bdbff0178564394e8f57d0fPaul Westbrook public abstract static class ConversationProvider extends ContentProvider { 142377177b171c483d485bdbff0178564394e8f57d0fPaul Westbrook public static String AUTHORITY; 14243663453628ab545bcba3327d972eecac72b6c2d0Andy Huang public static String sUriPrefix; 14253663453628ab545bcba3327d972eecac72b6c2d0Andy Huang public static final String URI_SEPARATOR = "://"; 14267460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal private ContentResolver mResolver; 142777177b171c483d485bdbff0178564394e8f57d0fPaul Westbrook 142877177b171c483d485bdbff0178564394e8f57d0fPaul Westbrook /** 14296a62146d0af3bf33ff472e4fbfad64f6c582dd8eVikram Aggarwal * Allows the implementing provider to specify the authority that should be used. 143077177b171c483d485bdbff0178564394e8f57d0fPaul Westbrook */ 143177177b171c483d485bdbff0178564394e8f57d0fPaul Westbrook protected abstract String getAuthority(); 14328d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank 1433c8a994227b9c686d88ee05840544162711a85712Marc Blank @Override 1434c8a994227b9c686d88ee05840544162711a85712Marc Blank public boolean onCreate() { 14358d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank sProvider = this; 143677177b171c483d485bdbff0178564394e8f57d0fPaul Westbrook AUTHORITY = getAuthority(); 14373663453628ab545bcba3327d972eecac72b6c2d0Andy Huang sUriPrefix = "content://" + AUTHORITY + "/"; 14387460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal mResolver = getContext().getContentResolver(); 14398d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank return true; 1440c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1441c8a994227b9c686d88ee05840544162711a85712Marc Blank 1442c8a994227b9c686d88ee05840544162711a85712Marc Blank @Override 1443c8a994227b9c686d88ee05840544162711a85712Marc Blank public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 1444c8a994227b9c686d88ee05840544162711a85712Marc Blank String sortOrder) { 14457460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal return mResolver.query( 1446c8a994227b9c686d88ee05840544162711a85712Marc Blank uriFromCachingUri(uri), projection, selection, selectionArgs, sortOrder); 1447c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1448c8a994227b9c686d88ee05840544162711a85712Marc Blank 1449c8a994227b9c686d88ee05840544162711a85712Marc Blank @Override 1450f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blank public Uri insert(Uri uri, ContentValues values) { 1451f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blank insertLocal(uri, values); 14527460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal return ProviderExecute.opInsert(mResolver, uri, values); 1453f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blank } 1454f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blank 1455f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blank @Override 1456f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blank public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 145781230ee24b65099109ee91add5a137d3795c454aVikram Aggarwal throw new IllegalStateException("Unexpected call to ConversationProvider.update"); 1458f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blank } 1459f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blank 1460f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blank @Override 1461f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blank public int delete(Uri uri, String selection, String[] selectionArgs) { 1462bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook throw new IllegalStateException("Unexpected call to ConversationProvider.delete"); 1463f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blank } 1464f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blank 1465f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blank @Override 1466c8a994227b9c686d88ee05840544162711a85712Marc Blank public String getType(Uri uri) { 1467c8a994227b9c686d88ee05840544162711a85712Marc Blank return null; 1468c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1469c8a994227b9c686d88ee05840544162711a85712Marc Blank 1470c8a994227b9c686d88ee05840544162711a85712Marc Blank /** 1471c8a994227b9c686d88ee05840544162711a85712Marc Blank * Quick and dirty class that executes underlying provider CRUD operations on a background 1472c8a994227b9c686d88ee05840544162711a85712Marc Blank * thread. 1473c8a994227b9c686d88ee05840544162711a85712Marc Blank */ 1474c8a994227b9c686d88ee05840544162711a85712Marc Blank static class ProviderExecute implements Runnable { 1475c8a994227b9c686d88ee05840544162711a85712Marc Blank static final int DELETE = 0; 1476c8a994227b9c686d88ee05840544162711a85712Marc Blank static final int INSERT = 1; 1477c8a994227b9c686d88ee05840544162711a85712Marc Blank static final int UPDATE = 2; 1478c8a994227b9c686d88ee05840544162711a85712Marc Blank 1479c8a994227b9c686d88ee05840544162711a85712Marc Blank final int mCode; 1480c8a994227b9c686d88ee05840544162711a85712Marc Blank final Uri mUri; 1481c8a994227b9c686d88ee05840544162711a85712Marc Blank final ContentValues mValues; //HEHEH 14827460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal final ContentResolver mResolver; 1483c8a994227b9c686d88ee05840544162711a85712Marc Blank 14847460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal ProviderExecute(int code, ContentResolver resolver, Uri uri, ContentValues values) { 1485c8a994227b9c686d88ee05840544162711a85712Marc Blank mCode = code; 1486c8a994227b9c686d88ee05840544162711a85712Marc Blank mUri = uriFromCachingUri(uri); 1487c8a994227b9c686d88ee05840544162711a85712Marc Blank mValues = values; 14887460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal mResolver = resolver; 1489c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1490c8a994227b9c686d88ee05840544162711a85712Marc Blank 14917460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal static Uri opInsert(ContentResolver resolver, Uri uri, ContentValues values) { 14927460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal ProviderExecute e = new ProviderExecute(INSERT, resolver, uri, values); 14938d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank if (offUiThread()) return (Uri)e.go(); 14948d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank new Thread(e).start(); 14958d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank return null; 14968d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank } 14978d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank 1498c8a994227b9c686d88ee05840544162711a85712Marc Blank @Override 1499c8a994227b9c686d88ee05840544162711a85712Marc Blank public void run() { 15008d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank go(); 15018d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank } 15028d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank 15038d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank public Object go() { 1504c8a994227b9c686d88ee05840544162711a85712Marc Blank switch(mCode) { 1505c8a994227b9c686d88ee05840544162711a85712Marc Blank case DELETE: 15067460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal return mResolver.delete(mUri, null, null); 1507c8a994227b9c686d88ee05840544162711a85712Marc Blank case INSERT: 15087460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal return mResolver.insert(mUri, mValues); 1509c8a994227b9c686d88ee05840544162711a85712Marc Blank case UPDATE: 15107460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal return mResolver.update(mUri, mValues, null, null); 15118d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank default: 15128d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank return null; 1513c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1514c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1515c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1516c8a994227b9c686d88ee05840544162711a85712Marc Blank 15178d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank private void insertLocal(Uri uri, ContentValues values) { 15188d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank // Placeholder for now; there's no local insert 15198d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank } 15208d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank 15212596f0011dcb4e86b01fc8a6362b5ac922239571Marc Blank private int mUndoSequence = 0; 15222596f0011dcb4e86b01fc8a6362b5ac922239571Marc Blank private ArrayList<Uri> mUndoDeleteUris = new ArrayList<Uri>(); 152330c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao private UndoCallback mUndoCallback = null; 15242596f0011dcb4e86b01fc8a6362b5ac922239571Marc Blank 152530c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao void addToUndoSequence(Uri uri, UndoCallback undoCallback) { 15262596f0011dcb4e86b01fc8a6362b5ac922239571Marc Blank if (sSequence != mUndoSequence) { 15272596f0011dcb4e86b01fc8a6362b5ac922239571Marc Blank mUndoSequence = sSequence; 15282596f0011dcb4e86b01fc8a6362b5ac922239571Marc Blank mUndoDeleteUris.clear(); 152930c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao mUndoCallback = undoCallback; 15302596f0011dcb4e86b01fc8a6362b5ac922239571Marc Blank } 15312596f0011dcb4e86b01fc8a6362b5ac922239571Marc Blank mUndoDeleteUris.add(uri); 15322596f0011dcb4e86b01fc8a6362b5ac922239571Marc Blank } 15332596f0011dcb4e86b01fc8a6362b5ac922239571Marc Blank 15342596f0011dcb4e86b01fc8a6362b5ac922239571Marc Blank @VisibleForTesting 153530c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao void deleteLocal(Uri uri, ConversationCursor conversationCursor, 153630c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao UndoCallback undoCallback) { 1537e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank String uriString = uriStringFromCachingUri(uri); 1538bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook conversationCursor.cacheValue(uriString, DELETED_COLUMN, true); 153930c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao addToUndoSequence(uri, undoCallback); 1540e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 1541e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank 1542e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank @VisibleForTesting 1543bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook void undeleteLocal(Uri uri, ConversationCursor conversationCursor) { 1544e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank String uriString = uriStringFromCachingUri(uri); 1545bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook conversationCursor.cacheValue(uriString, DELETED_COLUMN, false); 15462596f0011dcb4e86b01fc8a6362b5ac922239571Marc Blank } 15472596f0011dcb4e86b01fc8a6362b5ac922239571Marc Blank 154830c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao void setMostlyDead(Conversation conv, ConversationCursor conversationCursor, 154930c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao UndoCallback undoCallback) { 1550e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank Uri uri = conv.uri; 1551e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank String uriString = uriStringFromCachingUri(uri); 1552bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook conversationCursor.setMostlyDead(uriString, conv); 155330c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao addToUndoSequence(uri, undoCallback); 1554e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 1555e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank 1556bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook void commitMostlyDead(Conversation conv, ConversationCursor conversationCursor) { 1557bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook conversationCursor.commitMostlyDead(conv); 1558e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 1559e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank 1560bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook boolean clearMostlyDead(Uri uri, ConversationCursor conversationCursor) { 1561c3ccebce4a82e103ce7cae618166ca435c9969a0Alice Yang String uriString = uriStringFromCachingUri(uri); 1562bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return conversationCursor.clearMostlyDead(uriString); 1563e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 1564e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank 1565bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook public void undo(ConversationCursor conversationCursor) { 1566c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang if (mUndoSequence == 0) { 1567c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang return; 1568c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang } 1569c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang 1570c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang for (Uri uri: mUndoDeleteUris) { 1571c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang if (!clearMostlyDead(uri, conversationCursor)) { 1572c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang undeleteLocal(uri, conversationCursor); 15732596f0011dcb4e86b01fc8a6362b5ac922239571Marc Blank } 15742596f0011dcb4e86b01fc8a6362b5ac922239571Marc Blank } 1575c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang mUndoSequence = 0; 1576c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang conversationCursor.recalibratePosition(); 1577c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang // Notify listeners that there was a change to the underlying 1578c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang // cursor to add back in some items. 1579c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang conversationCursor.notifyDataChanged(); 158030c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao 158130c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao // If the caller specified an undo callback, call it here 158230c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao if (mUndoCallback != null) { 158330c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao mUndoCallback.performUndoCallback(); 158430c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao } 1585c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1586c8a994227b9c686d88ee05840544162711a85712Marc Blank 1587248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank @VisibleForTesting 1588bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook void updateLocal(Uri uri, ContentValues values, ConversationCursor conversationCursor) { 1589f98b318572750dae998a55a60199598933770b68Mindy Pereira if (values == null) { 1590f98b318572750dae998a55a60199598933770b68Mindy Pereira return; 1591f98b318572750dae998a55a60199598933770b68Mindy Pereira } 1592e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank String uriString = uriStringFromCachingUri(uri); 1593c8a994227b9c686d88ee05840544162711a85712Marc Blank for (String columnName: values.keySet()) { 1594bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook conversationCursor.cacheValue(uriString, columnName, values.get(columnName)); 1595c8a994227b9c686d88ee05840544162711a85712Marc Blank } 15968d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank } 15978d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank 1598cc754b102ed65df9d637b4e0a03e645214ea3cccVikram Aggarwal public int apply(Collection<ConversationOperation> ops, 1599bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook ConversationCursor conversationCursor) { 16008d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank final HashMap<String, ArrayList<ContentProviderOperation>> batchMap = 16018d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank new HashMap<String, ArrayList<ContentProviderOperation>>(); 1602b31ab5aea601f8aa5136a99edc23fb4d907f792eMarc Blank // Increment sequence count 1603b31ab5aea601f8aa5136a99edc23fb4d907f792eMarc Blank sSequence++; 160493b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank 1605f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blank // Execute locally and build CPO's for underlying provider 160693b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank boolean recalibrateRequired = false; 16078d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank for (ConversationOperation op: ops) { 16088d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank Uri underlyingUri = uriFromCachingUri(op.mUri); 16098d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank String authority = underlyingUri.getAuthority(); 16108d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank ArrayList<ContentProviderOperation> authOps = batchMap.get(authority); 16118d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank if (authOps == null) { 16128d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank authOps = new ArrayList<ContentProviderOperation>(); 16138d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank batchMap.put(authority, authOps); 16148d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank } 1615e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank ContentProviderOperation cpo = op.execute(underlyingUri); 1616e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank if (cpo != null) { 1617e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank authOps.add(cpo); 1618e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 161993b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank // Keep track of whether our operations require recalibrating the cursor position 162093b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank if (op.mRecalibrateRequired) { 162193b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank recalibrateRequired = true; 162293b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank } 16238d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank } 1624f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blank 162593b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank // Recalibrate cursor position if required 162693b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank if (recalibrateRequired) { 1627bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook conversationCursor.recalibratePosition(); 162893b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank } 1629958bf4d37a70f456dcda1530f9bb357d88c79300Marc Blank 1630fde56a76d6e10958ae8ba19d0a5d8b8df3b601a0Marc Blank // Notify listeners that data has changed 1631fde56a76d6e10958ae8ba19d0a5d8b8df3b601a0Marc Blank conversationCursor.notifyDataChanged(); 1632fde56a76d6e10958ae8ba19d0a5d8b8df3b601a0Marc Blank 1633f892f0a57d5c24b09fdc805f0fe2007ecd0d0e91Marc Blank // Send changes to underlying provider 16340823542b7e2238a764845bc22260d072f638b016Vikram Aggarwal final boolean notUiThread = offUiThread(); 16350823542b7e2238a764845bc22260d072f638b016Vikram Aggarwal for (final String authority: batchMap.keySet()) { 16360823542b7e2238a764845bc22260d072f638b016Vikram Aggarwal final ArrayList<ContentProviderOperation> opList = batchMap.get(authority); 16370823542b7e2238a764845bc22260d072f638b016Vikram Aggarwal if (notUiThread) { 16380823542b7e2238a764845bc22260d072f638b016Vikram Aggarwal try { 16397460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal mResolver.applyBatch(authority, opList); 16400823542b7e2238a764845bc22260d072f638b016Vikram Aggarwal } catch (RemoteException e) { 16410823542b7e2238a764845bc22260d072f638b016Vikram Aggarwal } catch (OperationApplicationException e) { 16428d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank } 16430823542b7e2238a764845bc22260d072f638b016Vikram Aggarwal } else { 16440823542b7e2238a764845bc22260d072f638b016Vikram Aggarwal new Thread(new Runnable() { 16450823542b7e2238a764845bc22260d072f638b016Vikram Aggarwal @Override 16460823542b7e2238a764845bc22260d072f638b016Vikram Aggarwal public void run() { 16470823542b7e2238a764845bc22260d072f638b016Vikram Aggarwal try { 16487460a1c7b617a6a67d3f24c699d0b16a78a3c02cVikram Aggarwal mResolver.applyBatch(authority, opList); 16490823542b7e2238a764845bc22260d072f638b016Vikram Aggarwal } catch (RemoteException e) { 16500823542b7e2238a764845bc22260d072f638b016Vikram Aggarwal } catch (OperationApplicationException e) { 16510823542b7e2238a764845bc22260d072f638b016Vikram Aggarwal } 16520823542b7e2238a764845bc22260d072f638b016Vikram Aggarwal } 16530823542b7e2238a764845bc22260d072f638b016Vikram Aggarwal }).start(); 16548d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank } 16558d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank } 16561b9efd9beb715927e0db7b138ddbb2925bc2c06fMarc Blank return sSequence; 16578d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank } 16588d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank } 16598d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank 1660bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook void setMostlyDead(String uriString, Conversation conv) { 1661d6020119c71ef9fe38725d0f0291cac7b9df6e79Vikram Aggarwal LogUtils.d(LOG_TAG, "[Mostly dead, deferring: %s] ", uriString); 1662bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook cacheValue(uriString, 1663bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook UIProvider.ConversationColumns.FLAGS, Conversation.FLAG_MOSTLY_DEAD); 1664bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook conv.convFlags |= Conversation.FLAG_MOSTLY_DEAD; 16658d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal mMostlyDead.add(conv); 1666bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mDeferSync = true; 1667bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 1668bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 1669bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook void commitMostlyDead(Conversation conv) { 1670bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook conv.convFlags &= ~Conversation.FLAG_MOSTLY_DEAD; 16718d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal mMostlyDead.remove(conv); 1672d6020119c71ef9fe38725d0f0291cac7b9df6e79Vikram Aggarwal LogUtils.d(LOG_TAG, "[All dead: %s]", conv.uri); 16738d9313bf75dc62b613d1c8a0b3f153f04171274fVikram Aggarwal if (mMostlyDead.isEmpty()) { 1674bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook mDeferSync = false; 1675bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook checkNotifyUI(); 1676bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 1677bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 1678bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 1679bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook boolean clearMostlyDead(String uriString) { 1680c3ccebce4a82e103ce7cae618166ca435c9969a0Alice Yang LogUtils.d(LOG_TAG, "[Clearing mostly dead %s] ", uriString); 1681c3ccebce4a82e103ce7cae618166ca435c9969a0Alice Yang mMostlyDead.clear(); 1682c3ccebce4a82e103ce7cae618166ca435c9969a0Alice Yang mDeferSync = false; 1683bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook Object val = getCachedValue(uriString, 1684bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook UIProvider.CONVERSATION_FLAGS_COLUMN); 1685bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook if (val != null) { 1686bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook int flags = ((Integer)val).intValue(); 1687bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook if ((flags & Conversation.FLAG_MOSTLY_DEAD) != 0) { 1688bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook cacheValue(uriString, UIProvider.ConversationColumns.FLAGS, 1689bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook flags &= ~Conversation.FLAG_MOSTLY_DEAD); 1690bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return true; 1691bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 1692bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 1693bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return false; 1694bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 1695bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 1696bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 1697bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 1698bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 16998d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank /** 17008d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank * ConversationOperation is the encapsulation of a ContentProvider operation to be performed 17018d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank * atomically as part of a "batch" operation. 17028d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank */ 1703bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook public class ConversationOperation { 1704e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank private static final int MOSTLY = 0x80; 17058d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank public static final int DELETE = 0; 17068d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank public static final int INSERT = 1; 17078d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank public static final int UPDATE = 2; 1708f98b318572750dae998a55a60199598933770b68Mindy Pereira public static final int ARCHIVE = 3; 1709830c00f18b199f4eb2b2cc05b7038b8b05d8eca9Mindy Pereira public static final int MUTE = 4; 1710830c00f18b199f4eb2b2cc05b7038b8b05d8eca9Mindy Pereira public static final int REPORT_SPAM = 5; 171177eee625cd8c317c605acfd06cd3a7e22120a0fdPaul Westbrook public static final int REPORT_NOT_SPAM = 6; 171276b2062b8c6d18a7b3a05292c385b47b0fcbd09fPaul Westbrook public static final int REPORT_PHISHING = 7; 1713ef3625472596326b910a4445307d1a8eb8c9cc3fPaul Westbrook public static final int DISCARD_DRAFTS = 8; 1714512821c11d89d49908f3cfdee0b582601f500f3dJin Cao public static final int MOVE_FAILED_INTO_DRAFTS = 9; 1715e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank public static final int MOSTLY_ARCHIVE = MOSTLY | ARCHIVE; 1716e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank public static final int MOSTLY_DELETE = MOSTLY | DELETE; 171706642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira public static final int MOSTLY_DESTRUCTIVE_UPDATE = MOSTLY | UPDATE; 17188d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank 17198d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank private final int mType; 17208d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank private final Uri mUri; 1721e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank private final Conversation mConversation; 17228d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank private final ContentValues mValues; 172330c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao // Callback handler for when this operation is undone 172430c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao private final UndoCallback mUndoCallback; 172530c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao 1726ce53818e1e185a845bd2f7f601c20e7085b40725Marc Blank // True if an updated item should be removed locally (from ConversationCursor) 172730fd47bf1947da5ad813cb957b6cbe569dce563aMindy Pereira // This would be the case for a folder change in which the conversation is no longer 1728ce53818e1e185a845bd2f7f601c20e7085b40725Marc Blank // in the folder represented by the ConversationCursor 1729ce53818e1e185a845bd2f7f601c20e7085b40725Marc Blank private final boolean mLocalDeleteOnUpdate; 173093b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank // After execution, this indicates whether or not the operation requires recalibration of 173193b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank // the current cursor position (i.e. it removed or added items locally) 173293b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank private boolean mRecalibrateRequired = true; 1733e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank // Whether this item is already mostly dead 1734e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank private final boolean mMostlyDead; 17358d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank 173630c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao public ConversationOperation(int type, Conversation conv, UndoCallback undoCallback) { 173730c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao this(type, conv, null, undoCallback); 17388d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank } 17398d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank 174030c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao public ConversationOperation(int type, Conversation conv, ContentValues values, 174130c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao UndoCallback undoCallback) { 17428d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank mType = type; 1743c43bc0a606e41144a780c4f873b5450e0ede0c91Marc Blank mUri = conv.uri; 1744e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank mConversation = conv; 17458d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank mValues = values; 174630c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao mUndoCallback = undoCallback; 1747ce53818e1e185a845bd2f7f601c20e7085b40725Marc Blank mLocalDeleteOnUpdate = conv.localDeleteOnUpdate; 1748e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank mMostlyDead = conv.isMostlyDead(); 17498d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank } 17508d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank 17518d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank private ContentProviderOperation execute(Uri underlyingUri) { 1752b31ab5aea601f8aa5136a99edc23fb4d907f792eMarc Blank Uri uri = underlyingUri.buildUpon() 1753dd10bc8736282262da0cd9a5f9a0236c10b47028Marc Blank .appendQueryParameter(UIProvider.SEQUENCE_QUERY_PARAMETER, 1754dd10bc8736282262da0cd9a5f9a0236c10b47028Marc Blank Integer.toString(sSequence)) 1755b31ab5aea601f8aa5136a99edc23fb4d907f792eMarc Blank .build(); 1756e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank ContentProviderOperation op = null; 17578d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank switch(mType) { 17588d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank case UPDATE: 1759ce53818e1e185a845bd2f7f601c20e7085b40725Marc Blank if (mLocalDeleteOnUpdate) { 176030c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao sProvider.deleteLocal(mUri, ConversationCursor.this, mUndoCallback); 1761ce53818e1e185a845bd2f7f601c20e7085b40725Marc Blank } else { 1762bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook sProvider.updateLocal(mUri, mValues, ConversationCursor.this); 176393b3a157b0a9e7588474d4d24b1fcbb9762bdd04Marc Blank mRecalibrateRequired = false; 1764ce53818e1e185a845bd2f7f601c20e7085b40725Marc Blank } 176506642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira if (!mMostlyDead) { 176606642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira op = ContentProviderOperation.newUpdate(uri) 176706642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira .withValues(mValues) 176806642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira .build(); 176906642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira } else { 177006642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira sProvider.commitMostlyDead(mConversation, ConversationCursor.this); 177106642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira } 177206642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira break; 177306642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira case MOSTLY_DESTRUCTIVE_UPDATE: 177430c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao sProvider.setMostlyDead(mConversation, ConversationCursor.this, mUndoCallback); 177506642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira op = ContentProviderOperation.newUpdate(uri).withValues(mValues).build(); 1776397621b93f83f8933f7a29a9b6d7fe2b88ec4008Andy Huang break; 17778d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank case INSERT: 17788d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank sProvider.insertLocal(mUri, mValues); 1779397621b93f83f8933f7a29a9b6d7fe2b88ec4008Andy Huang op = ContentProviderOperation.newInsert(uri) 17808d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank .withValues(mValues).build(); 1781397621b93f83f8933f7a29a9b6d7fe2b88ec4008Andy Huang break; 1782e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank // Destructive actions below! 1783e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank // "Mostly" operations are reflected globally, but not locally, except to set 1784e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank // FLAG_MOSTLY_DEAD in the conversation itself 1785e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank case DELETE: 178630c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao sProvider.deleteLocal(mUri, ConversationCursor.this, mUndoCallback); 1787e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank if (!mMostlyDead) { 1788e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank op = ContentProviderOperation.newDelete(uri).build(); 1789e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } else { 1790bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook sProvider.commitMostlyDead(mConversation, ConversationCursor.this); 1791e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 1792e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank break; 1793e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank case MOSTLY_DELETE: 179430c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao sProvider.setMostlyDead(mConversation,ConversationCursor.this, mUndoCallback); 1795e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank op = ContentProviderOperation.newDelete(uri).build(); 1796e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank break; 1797f98b318572750dae998a55a60199598933770b68Mindy Pereira case ARCHIVE: 179830c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao sProvider.deleteLocal(mUri, ConversationCursor.this, mUndoCallback); 1799e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank if (!mMostlyDead) { 1800e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank // Create an update operation that represents archive 1801e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank op = ContentProviderOperation.newUpdate(uri).withValue( 1802e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank ConversationOperations.OPERATION_KEY, 1803e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank ConversationOperations.ARCHIVE) 1804e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank .build(); 1805e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } else { 1806bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook sProvider.commitMostlyDead(mConversation, ConversationCursor.this); 1807e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank } 1808e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank break; 1809e1d1b07cdb0026097eb80f6c2912a16353aacec1Marc Blank case MOSTLY_ARCHIVE: 181030c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao sProvider.setMostlyDead(mConversation, ConversationCursor.this, mUndoCallback); 1811334e64af904085984cdcbecbcbc18cf488a9ceaePaul Westbrook // Create an update operation that represents archive 1812397621b93f83f8933f7a29a9b6d7fe2b88ec4008Andy Huang op = ContentProviderOperation.newUpdate(uri).withValue( 1813334e64af904085984cdcbecbcbc18cf488a9ceaePaul Westbrook ConversationOperations.OPERATION_KEY, ConversationOperations.ARCHIVE) 1814334e64af904085984cdcbecbcbc18cf488a9ceaePaul Westbrook .build(); 1815397621b93f83f8933f7a29a9b6d7fe2b88ec4008Andy Huang break; 1816830c00f18b199f4eb2b2cc05b7038b8b05d8eca9Mindy Pereira case MUTE: 1817334e64af904085984cdcbecbcbc18cf488a9ceaePaul Westbrook if (mLocalDeleteOnUpdate) { 181830c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao sProvider.deleteLocal(mUri, ConversationCursor.this, mUndoCallback); 1819334e64af904085984cdcbecbcbc18cf488a9ceaePaul Westbrook } 1820334e64af904085984cdcbecbcbc18cf488a9ceaePaul Westbrook 1821334e64af904085984cdcbecbcbc18cf488a9ceaePaul Westbrook // Create an update operation that represents mute 1822397621b93f83f8933f7a29a9b6d7fe2b88ec4008Andy Huang op = ContentProviderOperation.newUpdate(uri).withValue( 1823334e64af904085984cdcbecbcbc18cf488a9ceaePaul Westbrook ConversationOperations.OPERATION_KEY, ConversationOperations.MUTE) 1824334e64af904085984cdcbecbcbc18cf488a9ceaePaul Westbrook .build(); 1825397621b93f83f8933f7a29a9b6d7fe2b88ec4008Andy Huang break; 1826830c00f18b199f4eb2b2cc05b7038b8b05d8eca9Mindy Pereira case REPORT_SPAM: 182777eee625cd8c317c605acfd06cd3a7e22120a0fdPaul Westbrook case REPORT_NOT_SPAM: 182830c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao sProvider.deleteLocal(mUri, ConversationCursor.this, mUndoCallback); 1829334e64af904085984cdcbecbcbc18cf488a9ceaePaul Westbrook 183077eee625cd8c317c605acfd06cd3a7e22120a0fdPaul Westbrook final String operation = mType == REPORT_SPAM ? 183177eee625cd8c317c605acfd06cd3a7e22120a0fdPaul Westbrook ConversationOperations.REPORT_SPAM : 183277eee625cd8c317c605acfd06cd3a7e22120a0fdPaul Westbrook ConversationOperations.REPORT_NOT_SPAM; 183377eee625cd8c317c605acfd06cd3a7e22120a0fdPaul Westbrook 1834334e64af904085984cdcbecbcbc18cf488a9ceaePaul Westbrook // Create an update operation that represents report spam 1835397621b93f83f8933f7a29a9b6d7fe2b88ec4008Andy Huang op = ContentProviderOperation.newUpdate(uri).withValue( 183677eee625cd8c317c605acfd06cd3a7e22120a0fdPaul Westbrook ConversationOperations.OPERATION_KEY, operation).build(); 1837397621b93f83f8933f7a29a9b6d7fe2b88ec4008Andy Huang break; 183876b2062b8c6d18a7b3a05292c385b47b0fcbd09fPaul Westbrook case REPORT_PHISHING: 183930c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao sProvider.deleteLocal(mUri, ConversationCursor.this, mUndoCallback); 184076b2062b8c6d18a7b3a05292c385b47b0fcbd09fPaul Westbrook 1841ef3625472596326b910a4445307d1a8eb8c9cc3fPaul Westbrook // Create an update operation that represents report phishing 184276b2062b8c6d18a7b3a05292c385b47b0fcbd09fPaul Westbrook op = ContentProviderOperation.newUpdate(uri).withValue( 184376b2062b8c6d18a7b3a05292c385b47b0fcbd09fPaul Westbrook ConversationOperations.OPERATION_KEY, 184476b2062b8c6d18a7b3a05292c385b47b0fcbd09fPaul Westbrook ConversationOperations.REPORT_PHISHING).build(); 184576b2062b8c6d18a7b3a05292c385b47b0fcbd09fPaul Westbrook break; 1846ef3625472596326b910a4445307d1a8eb8c9cc3fPaul Westbrook case DISCARD_DRAFTS: 184730c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao sProvider.deleteLocal(mUri, ConversationCursor.this, mUndoCallback); 1848ef3625472596326b910a4445307d1a8eb8c9cc3fPaul Westbrook 1849ef3625472596326b910a4445307d1a8eb8c9cc3fPaul Westbrook // Create an update operation that represents discarding drafts 1850ef3625472596326b910a4445307d1a8eb8c9cc3fPaul Westbrook op = ContentProviderOperation.newUpdate(uri).withValue( 1851ef3625472596326b910a4445307d1a8eb8c9cc3fPaul Westbrook ConversationOperations.OPERATION_KEY, 1852ef3625472596326b910a4445307d1a8eb8c9cc3fPaul Westbrook ConversationOperations.DISCARD_DRAFTS).build(); 1853ef3625472596326b910a4445307d1a8eb8c9cc3fPaul Westbrook break; 1854512821c11d89d49908f3cfdee0b582601f500f3dJin Cao case MOVE_FAILED_INTO_DRAFTS: 1855512821c11d89d49908f3cfdee0b582601f500f3dJin Cao sProvider.deleteLocal(mUri, ConversationCursor.this, mUndoCallback); 1856512821c11d89d49908f3cfdee0b582601f500f3dJin Cao 1857512821c11d89d49908f3cfdee0b582601f500f3dJin Cao // Create an update operation that represents removing current folder label 1858512821c11d89d49908f3cfdee0b582601f500f3dJin Cao // and adding the drafts folder label for all failed messages. 1859512821c11d89d49908f3cfdee0b582601f500f3dJin Cao op = ContentProviderOperation.newUpdate(uri).withValue( 1860512821c11d89d49908f3cfdee0b582601f500f3dJin Cao ConversationOperations.OPERATION_KEY, 1861512821c11d89d49908f3cfdee0b582601f500f3dJin Cao ConversationOperations.MOVE_FAILED_TO_DRAFTS).build(); 1862512821c11d89d49908f3cfdee0b582601f500f3dJin Cao break; 18638d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank default: 18648d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank throw new UnsupportedOperationException( 18658d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank "No such ConversationOperation type: " + mType); 18668d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank } 1867397621b93f83f8933f7a29a9b6d7fe2b88ec4008Andy Huang 1868397621b93f83f8933f7a29a9b6d7fe2b88ec4008Andy Huang return op; 1869c8a994227b9c686d88ee05840544162711a85712Marc Blank } 1870c8a994227b9c686d88ee05840544162711a85712Marc Blank } 187197bca7b52aa2840494753d700a641161099cde23Marc Blank 187297bca7b52aa2840494753d700a641161099cde23Marc Blank /** 187397bca7b52aa2840494753d700a641161099cde23Marc Blank * For now, a single listener can be associated with the cursor, and for now we'll just 187497bca7b52aa2840494753d700a641161099cde23Marc Blank * notify on deletions 187597bca7b52aa2840494753d700a641161099cde23Marc Blank */ 187697bca7b52aa2840494753d700a641161099cde23Marc Blank public interface ConversationListener { 1877bec5115726f24733a0a1577caaf05fb6e9ef9c6fMarc Blank /** 1878bec5115726f24733a0a1577caaf05fb6e9ef9c6fMarc Blank * Data in the underlying provider has changed; a refresh is required to sync up 1879bec5115726f24733a0a1577caaf05fb6e9ef9c6fMarc Blank */ 188048eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public void onRefreshRequired(); 1881bec5115726f24733a0a1577caaf05fb6e9ef9c6fMarc Blank /** 1882bec5115726f24733a0a1577caaf05fb6e9ef9c6fMarc Blank * We've completed a requested refresh of the underlying cursor 1883bec5115726f24733a0a1577caaf05fb6e9ef9c6fMarc Blank */ 188448eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public void onRefreshReady(); 1885bec5115726f24733a0a1577caaf05fb6e9ef9c6fMarc Blank /** 1886bec5115726f24733a0a1577caaf05fb6e9ef9c6fMarc Blank * The data underlying the cursor has changed; the UI should redraw the list 1887bec5115726f24733a0a1577caaf05fb6e9ef9c6fMarc Blank */ 1888bec5115726f24733a0a1577caaf05fb6e9ef9c6fMarc Blank public void onDataSetChanged(); 188948eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 189048eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 189148eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 189248eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public boolean isFirst() { 189348eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank throw new UnsupportedOperationException(); 189448eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 189548eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 189648eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 189748eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public boolean isLast() { 189848eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank throw new UnsupportedOperationException(); 189948eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 190048eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 190148eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 190248eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public boolean isBeforeFirst() { 190348eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank throw new UnsupportedOperationException(); 190448eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 190548eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 190648eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 190748eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public boolean isAfterLast() { 190848eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank throw new UnsupportedOperationException(); 190948eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 191048eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 191148eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 191248eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public int getColumnIndex(String columnName) { 1913bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return mUnderlyingCursor.getColumnIndex(columnName); 191448eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 191548eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 191648eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 191748eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException { 1918bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return mUnderlyingCursor.getColumnIndexOrThrow(columnName); 191948eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 192048eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 192148eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 192248eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public String getColumnName(int columnIndex) { 1923bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return mUnderlyingCursor.getColumnName(columnIndex); 192448eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 192548eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 192648eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 192748eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public String[] getColumnNames() { 1928bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return mUnderlyingCursor.getColumnNames(); 192948eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 193048eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 193148eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 193248eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public int getColumnCount() { 1933bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return mUnderlyingCursor.getColumnCount(); 193448eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 193548eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 193648eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 193748eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { 193848eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank throw new UnsupportedOperationException(); 193948eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 194048eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 194148eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 194248eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public int getType(int columnIndex) { 1943bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return mUnderlyingCursor.getType(columnIndex); 194448eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 194548eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 194648eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 194748eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public boolean isNull(int columnIndex) { 194848eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank throw new UnsupportedOperationException(); 194948eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 195048eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 195148eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 195248eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public void deactivate() { 195348eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank throw new UnsupportedOperationException(); 195448eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 195548eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 195648eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 195748eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public boolean isClosed() { 1958bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return mUnderlyingCursor == null || mUnderlyingCursor.isClosed(); 195948eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 196048eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 196148eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 196248eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public void registerContentObserver(ContentObserver observer) { 1963397621b93f83f8933f7a29a9b6d7fe2b88ec4008Andy Huang // Nope. We never notify of underlying changes on this channel, since the cursor watches 1964397621b93f83f8933f7a29a9b6d7fe2b88ec4008Andy Huang // internally and offers onRefreshRequired/onRefreshReady to accomplish the same thing. 196548eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 196648eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 196748eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 196848eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public void unregisterContentObserver(ContentObserver observer) { 1969397621b93f83f8933f7a29a9b6d7fe2b88ec4008Andy Huang // See above. 197048eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 197148eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 197248eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 197348eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public void registerDataSetObserver(DataSetObserver observer) { 19742c4e6dc9eced78a62081d2ec48e5fd618461ea81Andy Huang // Nope. We use ConversationListener to accomplish this. 197548eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 197648eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 197748eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 197848eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public void unregisterDataSetObserver(DataSetObserver observer) { 19792c4e6dc9eced78a62081d2ec48e5fd618461ea81Andy Huang // See above. 198048eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 198148eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 198248eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 1983adf9fcb5239d9194d52b5a4a554b6679cb2b31f5Alice Yang public Uri getNotificationUri() { 1984adf9fcb5239d9194d52b5a4a554b6679cb2b31f5Alice Yang if (mUnderlyingCursor == null) { 1985adf9fcb5239d9194d52b5a4a554b6679cb2b31f5Alice Yang return null; 1986adf9fcb5239d9194d52b5a4a554b6679cb2b31f5Alice Yang } else { 1987adf9fcb5239d9194d52b5a4a554b6679cb2b31f5Alice Yang return mUnderlyingCursor.getNotificationUri(); 1988adf9fcb5239d9194d52b5a4a554b6679cb2b31f5Alice Yang } 1989adf9fcb5239d9194d52b5a4a554b6679cb2b31f5Alice Yang } 1990adf9fcb5239d9194d52b5a4a554b6679cb2b31f5Alice Yang 1991adf9fcb5239d9194d52b5a4a554b6679cb2b31f5Alice Yang @Override 199248eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public void setNotificationUri(ContentResolver cr, Uri uri) { 199348eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank throw new UnsupportedOperationException(); 199448eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 199548eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 199648eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 199748eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public boolean getWantsAllOnMoveCalls() { 199848eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank throw new UnsupportedOperationException(); 199948eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 200048eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 200148eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 200248eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public Bundle getExtras() { 200370a70c92bca006af84177d025c32deee3aa6da3fMindy Pereira return mUnderlyingCursor != null ? mUnderlyingCursor.getExtras() : Bundle.EMPTY; 200448eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 200548eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 200648eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 200748eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public Bundle respond(Bundle extras) { 2008606a6a16188f75d99f811d366c14bd53d71e23f5Paul Westbrook if (mUnderlyingCursor != null) { 2009606a6a16188f75d99f811d366c14bd53d71e23f5Paul Westbrook return mUnderlyingCursor.respond(extras); 2010606a6a16188f75d99f811d366c14bd53d71e23f5Paul Westbrook } 2011606a6a16188f75d99f811d366c14bd53d71e23f5Paul Westbrook return Bundle.EMPTY; 201248eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank } 201348eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank 201448eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank @Override 201548eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank public boolean requery() { 201648eba7a1eedf6c06b7783d49f44e61fc117f69bcMarc Blank return true; 201797bca7b52aa2840494753d700a641161099cde23Marc Blank } 2018bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 2019bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook // Below are methods that update Conversation data (update/delete) 2020bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 20219e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public int updateBoolean(Conversation conversation, String columnName, boolean value) { 20229e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy return updateBoolean(Arrays.asList(conversation), columnName, value); 2023bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2024bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 2025bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook /** 2026bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook * Update an integer column for a group of conversations (see updateValues below) 2027bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook */ 20289e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public int updateInt(Collection<Conversation> conversations, String columnName, 20299e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy int value) { 203081230ee24b65099109ee91add5a137d3795c454aVikram Aggarwal if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) { 203181230ee24b65099109ee91add5a137d3795c454aVikram Aggarwal LogUtils.d(LOG_TAG, "ConversationCursor.updateInt(conversations=%s, columnName=%s)", 203281230ee24b65099109ee91add5a137d3795c454aVikram Aggarwal conversations.toArray(), columnName); 203381230ee24b65099109ee91add5a137d3795c454aVikram Aggarwal } 2034bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook ContentValues cv = new ContentValues(); 2035bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook cv.put(columnName, value); 20369e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy return updateValues(conversations, cv); 2037bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2038bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 2039bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook /** 2040bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook * Update a string column for a group of conversations (see updateValues below) 2041bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook */ 20429e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public int updateBoolean(Collection<Conversation> conversations, String columnName, 20439e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy boolean value) { 2044bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook ContentValues cv = new ContentValues(); 2045bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook cv.put(columnName, value); 20469e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy return updateValues(conversations, cv); 2047bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2048bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 2049bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook /** 2050bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook * Update a string column for a group of conversations (see updateValues below) 2051bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook */ 20529e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public int updateString(Collection<Conversation> conversations, String columnName, 20539e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy String value) { 20549e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy return updateStrings(conversations, new String[] { 205526746eb4acb159f3ce0d411c10d85a1de3e958a0Paul Westbrook columnName 20569e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy }, new String[] { 205726746eb4acb159f3ce0d411c10d85a1de3e958a0Paul Westbrook value 2058ebdfd98264104cb5a6888acd663970b7c0b31382Mindy Pereira }); 2059ebdfd98264104cb5a6888acd663970b7c0b31382Mindy Pereira } 2060ebdfd98264104cb5a6888acd663970b7c0b31382Mindy Pereira 2061ebdfd98264104cb5a6888acd663970b7c0b31382Mindy Pereira /** 2062ebdfd98264104cb5a6888acd663970b7c0b31382Mindy Pereira * Update a string columns for a group of conversations (see updateValues below) 2063ebdfd98264104cb5a6888acd663970b7c0b31382Mindy Pereira */ 20649e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public int updateStrings(Collection<Conversation> conversations, 2065ebdfd98264104cb5a6888acd663970b7c0b31382Mindy Pereira String[] columnNames, String[] values) { 2066bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook ContentValues cv = new ContentValues(); 2067ebdfd98264104cb5a6888acd663970b7c0b31382Mindy Pereira for (int i = 0; i < columnNames.length; i++) { 2068ebdfd98264104cb5a6888acd663970b7c0b31382Mindy Pereira cv.put(columnNames[i], values[i]); 2069ebdfd98264104cb5a6888acd663970b7c0b31382Mindy Pereira } 20709e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy return updateValues(conversations, cv); 2071bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2072bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 2073bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook /** 2074bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook * Update a boolean column for a group of conversations, immediately in the UI and in a single 2075bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook * transaction in the underlying provider 20762c4e6dc9eced78a62081d2ec48e5fd618461ea81Andy Huang * @param conversations a collection of conversations 20772c4e6dc9eced78a62081d2ec48e5fd618461ea81Andy Huang * @param values the data to update 2078bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook * @return the sequence number of the operation (for undo) 2079bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook */ 20809e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public int updateValues(Collection<Conversation> conversations, ContentValues values) { 208130c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return updateValues(conversations, values, null); 208230c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao } 208330c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao 208430c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao public int updateValues(Collection<Conversation> conversations, ContentValues values, 208530c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao UndoCallback undoCallback) { 20869e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy return apply( 208730c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao getOperationsForConversations(conversations, ConversationOperation.UPDATE, values, 208830c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao undoCallback)); 2089bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2090bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 2091cc754b102ed65df9d637b4e0a03e645214ea3cccVikram Aggarwal /** 2092cc754b102ed65df9d637b4e0a03e645214ea3cccVikram Aggarwal * Apply many operations in a single batch transaction. 2093cc754b102ed65df9d637b4e0a03e645214ea3cccVikram Aggarwal * @param op the collection of operations obtained through successive calls to 209430c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao * {@link #getOperationForConversation(Conversation, int, ContentValues, UndoCallback)}. 2095cc754b102ed65df9d637b4e0a03e645214ea3cccVikram Aggarwal * @return the sequence number of the operation (for undo) 2096cc754b102ed65df9d637b4e0a03e645214ea3cccVikram Aggarwal */ 20979e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public int updateBulkValues(Collection<ConversationOperation> op) { 20989e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy return apply(op); 2099cc754b102ed65df9d637b4e0a03e645214ea3cccVikram Aggarwal } 2100cc754b102ed65df9d637b4e0a03e645214ea3cccVikram Aggarwal 2101bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook private ArrayList<ConversationOperation> getOperationsForConversations( 210230c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao Collection<Conversation> conversations, int type, ContentValues values, 210330c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao UndoCallback undoCallback) { 2104bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook final ArrayList<ConversationOperation> ops = Lists.newArrayList(); 2105bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook for (Conversation conv: conversations) { 210630c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao ops.add(getOperationForConversation(conv, type, values, undoCallback)); 2107bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2108bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return ops; 2109bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2110bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 2111cc754b102ed65df9d637b4e0a03e645214ea3cccVikram Aggarwal public ConversationOperation getOperationForConversation(Conversation conv, int type, 2112389f0b21a9b21bc3607b810c88272d06d48d0fe6mindyp ContentValues values) { 211330c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return getOperationForConversation(conv, type, values, null); 211430c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao } 211530c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao 211630c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao public ConversationOperation getOperationForConversation(Conversation conv, int type, 211730c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao ContentValues values, UndoCallback undoCallback) { 211830c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return new ConversationOperation(type, conv, values, undoCallback); 2119389f0b21a9b21bc3607b810c88272d06d48d0fe6mindyp } 2120389f0b21a9b21bc3607b810c88272d06d48d0fe6mindyp 21219e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public static void addFolderUpdates(ArrayList<Uri> folderUris, ArrayList<Boolean> add, 212226746eb4acb159f3ce0d411c10d85a1de3e958a0Paul Westbrook ContentValues values) { 2123cb0b30ee1d5cfcc267bc7de1e6ad78ed766c1e50mindyp ArrayList<String> folders = new ArrayList<String>(); 2124cb0b30ee1d5cfcc267bc7de1e6ad78ed766c1e50mindyp for (int i = 0; i < folderUris.size(); i++) { 2125cb0b30ee1d5cfcc267bc7de1e6ad78ed766c1e50mindyp folders.add(folderUris.get(i).buildUpon().appendPath(add.get(i) + "").toString()); 2126cb0b30ee1d5cfcc267bc7de1e6ad78ed766c1e50mindyp } 2127cb0b30ee1d5cfcc267bc7de1e6ad78ed766c1e50mindyp values.put(ConversationOperations.FOLDERS_UPDATED, 2128cb0b30ee1d5cfcc267bc7de1e6ad78ed766c1e50mindyp TextUtils.join(ConversationOperations.FOLDERS_UPDATED_SPLIT_PATTERN, folders)); 212926746eb4acb159f3ce0d411c10d85a1de3e958a0Paul Westbrook } 213026746eb4acb159f3ce0d411c10d85a1de3e958a0Paul Westbrook 21319e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public static void addTargetFolders(Collection<Folder> targetFolders, ContentValues values) { 2132b2033d855ab0f13e253e5403ce25989bcbc49488Andy Huang values.put(Conversation.UPDATE_FOLDER_COLUMN, FolderList.copyOf(targetFolders).toBlob()); 213326746eb4acb159f3ce0d411c10d85a1de3e958a0Paul Westbrook } 213426746eb4acb159f3ce0d411c10d85a1de3e958a0Paul Westbrook 213526746eb4acb159f3ce0d411c10d85a1de3e958a0Paul Westbrook public ConversationOperation getConversationFolderOperation(Conversation conv, 213626746eb4acb159f3ce0d411c10d85a1de3e958a0Paul Westbrook ArrayList<Uri> folderUris, ArrayList<Boolean> add, Collection<Folder> targetFolders) { 213730c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return getConversationFolderOperation(conv, folderUris, add, targetFolders, null, null); 21385cc0ab20009100e3ef259fe3d2e3ddc357f79285mindyp } 21395cc0ab20009100e3ef259fe3d2e3ddc357f79285mindyp 21405cc0ab20009100e3ef259fe3d2e3ddc357f79285mindyp public ConversationOperation getConversationFolderOperation(Conversation conv, 21415cc0ab20009100e3ef259fe3d2e3ddc357f79285mindyp ArrayList<Uri> folderUris, ArrayList<Boolean> add, Collection<Folder> targetFolders, 21425cc0ab20009100e3ef259fe3d2e3ddc357f79285mindyp ContentValues values) { 214330c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return getConversationFolderOperation(conv, folderUris, add, targetFolders, values, null); 214430c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao } 214530c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao 214630c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao public ConversationOperation getConversationFolderOperation(Conversation conv, 214730c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao ArrayList<Uri> folderUris, ArrayList<Boolean> add, Collection<Folder> targetFolders, 214830c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao UndoCallback undoCallback) { 214930c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return getConversationFolderOperation(conv, folderUris, add, targetFolders, 215030c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao new ContentValues(), undoCallback); 215130c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao } 215230c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao 215330c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao public ConversationOperation getConversationFolderOperation(Conversation conv, 215430c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao ArrayList<Uri> folderUris, ArrayList<Boolean> add, Collection<Folder> targetFolders, 215530c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao ContentValues values, UndoCallback undoCallback) { 215626746eb4acb159f3ce0d411c10d85a1de3e958a0Paul Westbrook addFolderUpdates(folderUris, add, values); 215726746eb4acb159f3ce0d411c10d85a1de3e958a0Paul Westbrook addTargetFolders(targetFolders, values); 215830c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return getOperationForConversation(conv, ConversationOperation.UPDATE, values, 215930c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao undoCallback); 2160cb0b30ee1d5cfcc267bc7de1e6ad78ed766c1e50mindyp } 2161cb0b30ee1d5cfcc267bc7de1e6ad78ed766c1e50mindyp 2162bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook // Convenience methods 21639e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy private int apply(Collection<ConversationOperation> operations) { 2164bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook return sProvider.apply(operations, this); 2165bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2166bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 2167bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook private void undoLocal() { 2168bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook sProvider.undo(this); 2169bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2170bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 2171bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook public void undo(final Context context, final Uri undoUri) { 2172bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook new Thread(new Runnable() { 2173bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook @Override 2174bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook public void run() { 2175bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook Cursor c = context.getContentResolver().query(undoUri, UIProvider.UNDO_PROJECTION, 2176bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook null, null, null); 2177bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook if (c != null) { 2178bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook c.close(); 2179bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2180bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2181bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook }).start(); 2182bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook undoLocal(); 2183bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2184bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 2185bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook /** 2186bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook * Delete a group of conversations immediately in the UI and in a single transaction in the 2187bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook * underlying provider. See applyAction for argument descriptions 2188bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook */ 21899e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public int delete(Collection<Conversation> conversations) { 219030c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return delete(conversations, null); 219130c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao } 219230c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao 219330c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao public int delete(Collection<Conversation> conversations, UndoCallback undoCallback) { 219430c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return applyAction(conversations, ConversationOperation.DELETE, undoCallback); 2195bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2196bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 2197bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook /** 2198bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook * As above, for archive 2199bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook */ 22009e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public int archive(Collection<Conversation> conversations) { 220130c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return archive(conversations, null); 220230c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao } 220330c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao 220430c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao public int archive(Collection<Conversation> conversations, UndoCallback undoCallback) { 220530c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return applyAction(conversations, ConversationOperation.ARCHIVE, undoCallback); 2206bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2207bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 2208bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook /** 2209bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook * As above, for mute 2210bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook */ 22119e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public int mute(Collection<Conversation> conversations) { 221230c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return mute(conversations, null); 221330c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao } 221430c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao 221530c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao public int mute(Collection<Conversation> conversations, UndoCallback undoCallback) { 221630c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return applyAction(conversations, ConversationOperation.MUTE, undoCallback); 2217bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2218bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 2219bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook /** 2220bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook * As above, for report spam 2221bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook */ 22229e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public int reportSpam(Collection<Conversation> conversations) { 222330c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return reportSpam(conversations, null); 222430c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao } 222530c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao 222630c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao public int reportSpam(Collection<Conversation> conversations, UndoCallback undoCallback) { 222730c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return applyAction(conversations, ConversationOperation.REPORT_SPAM, undoCallback); 2228bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2229bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 2230bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook /** 223177eee625cd8c317c605acfd06cd3a7e22120a0fdPaul Westbrook * As above, for report not spam 223277eee625cd8c317c605acfd06cd3a7e22120a0fdPaul Westbrook */ 22339e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public int reportNotSpam(Collection<Conversation> conversations) { 223430c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return reportNotSpam(conversations, null); 223530c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao } 223630c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao 223730c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao public int reportNotSpam(Collection<Conversation> conversations, UndoCallback undoCallback) { 223830c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return applyAction(conversations, ConversationOperation.REPORT_NOT_SPAM, undoCallback); 223977eee625cd8c317c605acfd06cd3a7e22120a0fdPaul Westbrook } 224077eee625cd8c317c605acfd06cd3a7e22120a0fdPaul Westbrook 224177eee625cd8c317c605acfd06cd3a7e22120a0fdPaul Westbrook /** 224276b2062b8c6d18a7b3a05292c385b47b0fcbd09fPaul Westbrook * As above, for report phishing 224376b2062b8c6d18a7b3a05292c385b47b0fcbd09fPaul Westbrook */ 22449e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public int reportPhishing(Collection<Conversation> conversations) { 224530c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return reportPhishing(conversations, null); 224630c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao } 224730c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao 224830c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao public int reportPhishing(Collection<Conversation> conversations, UndoCallback undoCallback) { 224930c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return applyAction(conversations, ConversationOperation.REPORT_PHISHING, undoCallback); 225076b2062b8c6d18a7b3a05292c385b47b0fcbd09fPaul Westbrook } 225176b2062b8c6d18a7b3a05292c385b47b0fcbd09fPaul Westbrook 225276b2062b8c6d18a7b3a05292c385b47b0fcbd09fPaul Westbrook /** 2253ef3625472596326b910a4445307d1a8eb8c9cc3fPaul Westbrook * Discard the drafts in the specified conversations 2254ef3625472596326b910a4445307d1a8eb8c9cc3fPaul Westbrook */ 22559e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public int discardDrafts(Collection<Conversation> conversations) { 225630c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return discardDrafts(conversations, null); 225730c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao } 225830c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao 225930c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao public int discardDrafts(Collection<Conversation> conversations, UndoCallback undoCallback) { 226030c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return applyAction(conversations, ConversationOperation.DISCARD_DRAFTS, undoCallback); 2261ef3625472596326b910a4445307d1a8eb8c9cc3fPaul Westbrook } 2262ef3625472596326b910a4445307d1a8eb8c9cc3fPaul Westbrook 2263ef3625472596326b910a4445307d1a8eb8c9cc3fPaul Westbrook /** 2264512821c11d89d49908f3cfdee0b582601f500f3dJin Cao * Move the failed messages in the specified conversation from the current folder to drafts 2265512821c11d89d49908f3cfdee0b582601f500f3dJin Cao */ 2266512821c11d89d49908f3cfdee0b582601f500f3dJin Cao public int moveFailedIntoDrafts(Collection<Conversation> conversations) { 2267512821c11d89d49908f3cfdee0b582601f500f3dJin Cao // this operation does not permit undo 2268512821c11d89d49908f3cfdee0b582601f500f3dJin Cao return applyAction(conversations, ConversationOperation.MOVE_FAILED_INTO_DRAFTS, null); 2269512821c11d89d49908f3cfdee0b582601f500f3dJin Cao } 2270512821c11d89d49908f3cfdee0b582601f500f3dJin Cao 2271512821c11d89d49908f3cfdee0b582601f500f3dJin Cao /** 2272bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook * As above, for mostly archive 2273bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook */ 22749e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public int mostlyArchive(Collection<Conversation> conversations) { 227530c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return mostlyArchive(conversations, null); 227630c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao } 227730c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao 227830c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao public int mostlyArchive(Collection<Conversation> conversations, UndoCallback undoCallback) { 227930c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return applyAction(conversations, ConversationOperation.MOSTLY_ARCHIVE, undoCallback); 2280bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2281bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 2282bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook /** 2283bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook * As above, for mostly delete 2284bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook */ 22859e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public int mostlyDelete(Collection<Conversation> conversations) { 228630c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return mostlyDelete(conversations, null); 228730c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao } 228830c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao 228930c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao public int mostlyDelete(Collection<Conversation> conversations, UndoCallback undoCallback) { 229030c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return applyAction(conversations, ConversationOperation.MOSTLY_DELETE, undoCallback); 2291bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2292bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 2293bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook /** 2294ebdfd98264104cb5a6888acd663970b7c0b31382Mindy Pereira * As above, for mostly destructive updates. 2295ebdfd98264104cb5a6888acd663970b7c0b31382Mindy Pereira */ 22969e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy public int mostlyDestructiveUpdate(Collection<Conversation> conversations, 2297b2033d855ab0f13e253e5403ce25989bcbc49488Andy Huang ContentValues values) { 229830c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao return mostlyDestructiveUpdate(conversations, values, null); 229930c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao } 230030c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao 230130c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao public int mostlyDestructiveUpdate(Collection<Conversation> conversations, 230230c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao ContentValues values, UndoCallback undoCallback) { 230306642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira return apply( 230406642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira getOperationsForConversations(conversations, 230530c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao ConversationOperation.MOSTLY_DESTRUCTIVE_UPDATE, values, undoCallback)); 230606642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira } 230706642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira 230806642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira /** 2309bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook * Convenience method for performing an operation on a group of conversations 2310bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook * @param conversations the conversations to be affected 2311bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook * @param opAction the action to take 231230c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao * @param undoCallback the undo callback handler 2313bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook * @return the sequence number of the operation applied in CC 2314bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook */ 231530c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao private int applyAction(Collection<Conversation> conversations, int opAction, 231630c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao UndoCallback undoCallback) { 2317bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook ArrayList<ConversationOperation> ops = Lists.newArrayList(); 2318bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook for (Conversation conv: conversations) { 2319bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook ConversationOperation op = 232030c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao new ConversationOperation(opAction, conv, undoCallback); 2321bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook ops.add(op); 2322bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 23239e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy return apply(ops); 2324bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook } 2325bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 232681a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal /** 232781a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal * Do not make this method dependent on the internal mechanism of the cursor. 232881a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal * Currently just calls the parent implementation. If this is ever overriden, take care to 232981a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal * ensure that two references map to the same hashcode. If 233081a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal * ConversationCursor first == ConversationCursor second, 233181a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal * then 233281a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal * first.hashCode() == second.hashCode(). 233381a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal * The {@link ConversationListFragment} relies on this behavior of 233481a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal * {@link ConversationCursor#hashCode()} to avoid storing dangerous references to the cursor. 233581a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal * {@inheritDoc} 233681a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal */ 233781a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal @Override 233881a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal public int hashCode() { 233981a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal return super.hashCode(); 234081a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal } 234108a079c3d2857e365736432b2691187767eb116fScott Kennedy 23422992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang @Override 23432992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang public String toString() { 2344c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang final StringBuilder sb = new StringBuilder("{"); 2345c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang sb.append(super.toString()); 2346c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang sb.append(" mName="); 2347c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang sb.append(mName); 23482992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang sb.append(" mDeferSync="); 23492992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang sb.append(mDeferSync); 23502992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang sb.append(" mRefreshRequired="); 23512992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang sb.append(mRefreshRequired); 23522992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang sb.append(" mRefreshReady="); 23532992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang sb.append(mRefreshReady); 23542992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang sb.append(" mRefreshTask="); 23552992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang sb.append(mRefreshTask); 23562992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang sb.append(" mPaused="); 23572992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang sb.append(mPaused); 23582992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang sb.append(" mDeletedCount="); 23592992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang sb.append(mDeletedCount); 23602992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang sb.append(" mUnderlying="); 23612992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang sb.append(mUnderlyingCursor); 2362f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) { 2363f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang sb.append(" mCacheMap="); 2364f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang sb.append(mCacheMap); 2365f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang } 23662992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang sb.append("}"); 23672992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang return sb.toString(); 23682992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang } 23692992aa3cac250355c41bc6f3b4dd77b0ce4f1e1bAndy Huang 237008a079c3d2857e365736432b2691187767eb116fScott Kennedy private void resetNotificationActions() { 2371828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // Needs to be on the UI thread because it updates the ConversationCursor's internal 2372828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // state which violates assumptions about how the ListView works and how 2373828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // the ConversationViewPager works if performed off of the UI thread. 2374828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // Also, prevents ConcurrentModificationExceptions on mNotificationTempDeleted. 2375828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein mMainThreadHandler.post(new Runnable() { 2376828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein @Override 2377828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein public void run() { 2378828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein final boolean changed = !mNotificationTempDeleted.isEmpty(); 237908a079c3d2857e365736432b2691187767eb116fScott Kennedy 2380828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein for (final Conversation conversation : mNotificationTempDeleted) { 2381828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein sProvider.undeleteLocal(conversation.uri, ConversationCursor.this); 2382828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein } 238308a079c3d2857e365736432b2691187767eb116fScott Kennedy 2384828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein mNotificationTempDeleted.clear(); 238508a079c3d2857e365736432b2691187767eb116fScott Kennedy 2386828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein if (changed) { 238708a079c3d2857e365736432b2691187767eb116fScott Kennedy notifyDataChanged(); 238808a079c3d2857e365736432b2691187767eb116fScott Kennedy } 2389828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein } 2390828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein }); 239108a079c3d2857e365736432b2691187767eb116fScott Kennedy } 239208a079c3d2857e365736432b2691187767eb116fScott Kennedy 239308a079c3d2857e365736432b2691187767eb116fScott Kennedy /** 239408a079c3d2857e365736432b2691187767eb116fScott Kennedy * If a destructive notification action was triggered, but has not yet been processed because an 239508a079c3d2857e365736432b2691187767eb116fScott Kennedy * "Undo" action is available, we do not want to show the conversation in the list. 239608a079c3d2857e365736432b2691187767eb116fScott Kennedy */ 239708a079c3d2857e365736432b2691187767eb116fScott Kennedy public void handleNotificationActions() { 2398828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // Needs to be on the UI thread because it updates the ConversationCursor's internal 2399828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // state which violates assumptions about how the ListView works and how 2400828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // the ConversationViewPager works if performed off of the UI thread. 2401828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // Also, prevents ConcurrentModificationExceptions on mNotificationTempDeleted. 2402828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein mMainThreadHandler.post(new Runnable() { 2403828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein @Override 2404828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein public void run() { 2405828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein final SparseArrayCompat<NotificationAction> undoNotifications = 2406828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein NotificationActionUtils.sUndoNotifications; 2407828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein final Set<Conversation> undoneConversations = 2408828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein NotificationActionUtils.sUndoneConversations; 240908a079c3d2857e365736432b2691187767eb116fScott Kennedy 2410828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein final Set<Conversation> undoConversations = 2411828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein Sets.newHashSetWithExpectedSize(undoNotifications.size()); 241208a079c3d2857e365736432b2691187767eb116fScott Kennedy 2413828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein boolean changed = false; 241408a079c3d2857e365736432b2691187767eb116fScott Kennedy 2415828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein for (int i = 0; i < undoNotifications.size(); i++) { 2416828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein final NotificationAction notificationAction = 2417828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein undoNotifications.get(undoNotifications.keyAt(i)); 241808a079c3d2857e365736432b2691187767eb116fScott Kennedy 2419828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // We only care about notifications that were for this folder 2420828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // or if the action was delete 2421828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein final Folder folder = notificationAction.getFolder(); 2422828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein final boolean deleteAction = notificationAction.getNotificationActionType() 2423828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein == NotificationActionType.DELETE; 242408a079c3d2857e365736432b2691187767eb116fScott Kennedy 2425828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein if (folder.conversationListUri.equals(qUri) || deleteAction) { 2426828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // We only care about destructive actions 2427828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein if (notificationAction.getNotificationActionType().getIsDestructive()) { 2428828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein final Conversation conversation = notificationAction.getConversation(); 242908a079c3d2857e365736432b2691187767eb116fScott Kennedy 2430828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein undoConversations.add(conversation); 243108a079c3d2857e365736432b2691187767eb116fScott Kennedy 2432828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein if (!mNotificationTempDeleted.contains(conversation)) { 243330c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao sProvider.deleteLocal(conversation.uri, ConversationCursor.this, 243430c881a7d31203fc7b233abf4a0c1413de4cd517Jin Cao null); 2435828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein mNotificationTempDeleted.add(conversation); 243608a079c3d2857e365736432b2691187767eb116fScott Kennedy 2437828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein changed = true; 2438828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein } 2439828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein } 244008a079c3d2857e365736432b2691187767eb116fScott Kennedy } 244108a079c3d2857e365736432b2691187767eb116fScott Kennedy } 244208a079c3d2857e365736432b2691187767eb116fScott Kennedy 2443828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // Remove any conversations from the temporary deleted state 2444828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // if they no longer have an undo notification 2445828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein final Iterator<Conversation> iterator = mNotificationTempDeleted.iterator(); 2446828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein while (iterator.hasNext()) { 2447828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein final Conversation conversation = iterator.next(); 2448828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein 2449828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein if (!undoConversations.contains(conversation)) { 2450828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // We should only be un-deleting local cursor edits 2451828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // if the notification was undone rather than just 2452828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // disappearing because the internal cursor 2453828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // gets updated when the undo goes away via timeout which 2454828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein // will update everything properly. 2455828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein if (undoneConversations.contains(conversation)) { 2456828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein sProvider.undeleteLocal(conversation.uri, ConversationCursor.this); 2457828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein undoneConversations.remove(conversation); 2458828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein } 2459828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein iterator.remove(); 246008a079c3d2857e365736432b2691187767eb116fScott Kennedy 2461828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein changed = true; 2462828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein } 2463828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein } 246408a079c3d2857e365736432b2691187767eb116fScott Kennedy 2465828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein if (changed) { 246608a079c3d2857e365736432b2691187767eb116fScott Kennedy notifyDataChanged(); 246708a079c3d2857e365736432b2691187767eb116fScott Kennedy } 2468828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein } 2469828bc86ffcb1274fb461fbb25cdc2a07ae76b6c8Andrew Sapperstein }); 247008a079c3d2857e365736432b2691187767eb116fScott Kennedy } 2471b1e21487e18503cc110e1cf8726b835e048ab1c1Scott Kennedy 2472b1e21487e18503cc110e1cf8726b835e048ab1c1Scott Kennedy @Override 2473b1e21487e18503cc110e1cf8726b835e048ab1c1Scott Kennedy public void markContentsSeen() { 24747ee089ea2ef96e31504e88637b5f3b0969b3c7c1Scott Kennedy ConversationCursorOperationListener.OperationHelper.markContentsSeen(mUnderlyingCursor); 24757ee089ea2ef96e31504e88637b5f3b0969b3c7c1Scott Kennedy } 24767ee089ea2ef96e31504e88637b5f3b0969b3c7c1Scott Kennedy 24777ee089ea2ef96e31504e88637b5f3b0969b3c7c1Scott Kennedy @Override 24787ee089ea2ef96e31504e88637b5f3b0969b3c7c1Scott Kennedy public void emptyFolder() { 24797ee089ea2ef96e31504e88637b5f3b0969b3c7c1Scott Kennedy ConversationCursorOperationListener.OperationHelper.emptyFolder(mUnderlyingCursor); 2480b1e21487e18503cc110e1cf8726b835e048ab1c1Scott Kennedy } 2481bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao 2482bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao /** 2483bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao * Check if the provided cursor is ready to display anything in the UI. The return value tells 2484bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao * us if the cursor is ready to be displayed. 2485bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao * @param cursor 2486bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao * @return true if the cursor is partially/completely loaded with >0 count or completely loaded 2487bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao * and empty. 2488bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao */ 2489bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao public static boolean isCursorReadyToShow(ConversationCursor cursor) { 2490bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao if (cursor == null) { 2491bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao return false; 2492bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao } 2493bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao Bundle extras = cursor.getExtras(); 2494bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao final int status = (extras == null) ? UIProvider.CursorStatus.LOADING : 2495bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao extras.getInt(UIProvider.CursorExtraKeys.EXTRA_STATUS); 2496ee6766a2c6938dbb4c975d93732d7033eecd90a5Jin Cao return (cursor.getCount() > 0 || UIProvider.CursorStatus.ERROR == status || 2497ee6766a2c6938dbb4c975d93732d7033eecd90a5Jin Cao UIProvider.CursorStatus.COMPLETE == status); 2498bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao } 249955d664ada644a637ba13128b0fc50086f2abab8cVikram Aggarwal} 2500