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