ConversationSelectionSet.java revision 0a22d4482396f3717b36796e594d5f8e9760d509
14a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal/*******************************************************************************
24a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal *      Copyright (C) 2012 Google Inc.
34a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal *      Licensed to The Android Open Source Project.
44a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal *
54a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal *      Licensed under the Apache License, Version 2.0 (the "License");
64a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal *      you may not use this file except in compliance with the License.
74a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal *      You may obtain a copy of the License at
84a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal *
94a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal *           http://www.apache.org/licenses/LICENSE-2.0
104a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal *
114a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal *      Unless required by applicable law or agreed to in writing, software
124a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal *      distributed under the License is distributed on an "AS IS" BASIS,
134a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
144a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal *      See the License for the specific language governing permissions and
154a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal *      limitations under the License.
164a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal *******************************************************************************/
174a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
184a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalpackage com.android.mail.ui;
194a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
204a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalimport com.google.common.annotations.VisibleForTesting;
21fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrookimport com.google.common.collect.BiMap;
22fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrookimport com.google.common.collect.HashBiMap;
234a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalimport com.google.common.collect.Lists;
24fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrookimport com.google.common.collect.Sets;
254a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
264a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalimport android.os.Parcel;
274a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalimport android.os.Parcelable;
284a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
29866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereiraimport com.android.mail.browse.ConversationItemView;
30a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrookimport com.android.mail.browse.ConversationCursor;
311ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwalimport com.android.mail.providers.Conversation;
32a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrookimport com.android.mail.utils.Utils;
33a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
344a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalimport java.util.ArrayList;
354a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalimport java.util.Collection;
36a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrookimport java.util.Collections;
374a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalimport java.util.HashMap;
38a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrookimport java.util.HashSet;
39a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrookimport java.util.Set;
404a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
414a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal/**
424a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal * A simple thread-safe wrapper over a set of conversations representing a
434a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal * selection set (e.g. in a conversation list). This class dispatches changes
444a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal * when the set goes empty, and when it becomes unempty. For simplicity, this
454a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal * class <b>does not allow modifications</b> to the collection in observers when
464a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal * responding to change events.
474a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal */
484a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalpublic class ConversationSelectionSet implements Parcelable {
491ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    public static final Parcelable.Creator<ConversationSelectionSet> CREATOR =
501ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal            new Parcelable.Creator<ConversationSelectionSet>() {
511ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
521ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        @Override
531ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        public ConversationSelectionSet createFromParcel(Parcel source) {
541ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal            ConversationSelectionSet result = new ConversationSelectionSet();
551ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal            Parcelable[] conversations = source.readParcelableArray(
561ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal                            Conversation.class.getClassLoader());
571ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal            for (Parcelable parceled : conversations) {
58866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira                Conversation conversation = (Conversation) parceled;
59866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira                result.put(conversation.id, conversation);
601ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal            }
611ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal            return result;
621ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        }
631ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
641ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        @Override
651ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        public ConversationSelectionSet[] newArray(int size) {
661ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal            return new ConversationSelectionSet[size];
671ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        }
681ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    };
691ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
704e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private final Object mLock = new Object();
711ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    private final HashMap<Long, Conversation> mInternalMap =
721ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal            new HashMap<Long, Conversation>();
734a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
74c7e3a7db9ff9cb43241995001462764b70b75e96Vikram Aggarwal    /**
75c7e3a7db9ff9cb43241995001462764b70b75e96Vikram Aggarwal     * Map of conversation IDs to {@link ConversationItemView} objects. The views are <b>not</b>
76c7e3a7db9ff9cb43241995001462764b70b75e96Vikram Aggarwal     * updated when a new list view object is created on orientation change.
77c7e3a7db9ff9cb43241995001462764b70b75e96Vikram Aggarwal     */
78866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    private final HashMap<Long, ConversationItemView> mInternalViewMap =
79866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira            new HashMap<Long, ConversationItemView>();
80fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook    private final BiMap<String, Long> mConversationUriToIdMap = HashBiMap.create();
81866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
824a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    @VisibleForTesting
834a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    final ArrayList<ConversationSetObserver> mObservers = new ArrayList<ConversationSetObserver>();
844a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
854a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    /**
864a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal     * Registers an observer to listen for interesting changes on this set.
874a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal     *
884a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal     * @param observer the observer to register.
894a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal     */
904e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public void addObserver(ConversationSetObserver observer) {
914e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
924e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mObservers.add(observer);
934e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
944a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
954a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
96d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    /**
97d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * Clear the selected set entirely.
98d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     */
994e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public void clear() {
1004e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1014e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            boolean initiallyNotEmpty = !mInternalMap.isEmpty();
1024e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mInternalViewMap.clear();
1034e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mInternalMap.clear();
1044e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mConversationUriToIdMap.clear();
1054e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook
1064e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            if (mInternalMap.isEmpty() && initiallyNotEmpty) {
1074e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
1084e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                dispatchOnChange(observersCopy);
1094e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                dispatchOnEmpty(observersCopy);
1104e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
1111ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        }
1121ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    }
1131ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
114d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    /**
115d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * Returns true if the given key exists in the conversation selection set. This assumes
116d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * the internal representation holds conversation.id values.
117d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * @param key the id of the conversation
118d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * @return true if the key exists in this selected set.
119d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     */
1204e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public boolean containsKey(Long key) {
1214e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1224e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return mInternalMap.containsKey(key);
1234e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
1241ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    }
1251ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
126d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    /**
127d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * Returns true if the given conversation is stored in the selection set.
128d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * @param conversation
129d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * @return true if the conversation exists in the selected set.
130d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     */
1314e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public boolean contains(Conversation conversation) {
1324e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1334e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return containsKey(conversation.id);
1344e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
135d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    }
136d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal
1371ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    @Override
1381ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    public int describeContents() {
1391ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        return 0;
1401ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    }
1411ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
1424e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void dispatchOnBecomeUnempty(ArrayList<ConversationSetObserver> observers) {
1434e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1444e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            for (ConversationSetObserver observer : observers) {
1454e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                observer.onSetPopulated(this);
1464e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
1471ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        }
1484a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
1494a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
1504e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void dispatchOnChange(ArrayList<ConversationSetObserver> observers) {
1514e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1524e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // Copy observers so that they may unregister themselves as listeners on
1534e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // event handling.
1544e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            for (ConversationSetObserver observer : observers) {
1554e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                observer.onSetChanged(this);
1564e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
1574a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        }
1584a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
1594a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
1604e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void dispatchOnEmpty(ArrayList<ConversationSetObserver> observers) {
1614e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1624e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            for (ConversationSetObserver observer : observers) {
1634e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                observer.onSetEmpty();
1644e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
1654a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        }
1664a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
1674a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
1681ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    /**
1691ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     * Is this conversation set empty?
1701ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     * @return true if the conversation selection set is empty. False otherwise.
1711ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     */
1724e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public boolean isEmpty() {
1734e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1744e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return mInternalMap.isEmpty();
1754e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
1761ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    }
1771ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
1784e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void put(Long id, Conversation info) {
1794e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1804e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final boolean initiallyEmpty = mInternalMap.isEmpty();
1814e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mInternalMap.put(id, info);
1824e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // Fill out the view map with null. The sizes will match, but
1834e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // we won't have any views available yet to store.
1844e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mInternalViewMap.put(id, null);
1854e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mConversationUriToIdMap.put(info.uri.toString(), id);
186866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
1874e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
1884e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            dispatchOnChange(observersCopy);
1894e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            if (initiallyEmpty) {
1904e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                dispatchOnBecomeUnempty(observersCopy);
1914e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
192866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        }
193866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    }
194866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
195866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    /** @see java.util.HashMap#put */
1964e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void put(Long id, ConversationItemView info) {
1974e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1984e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            boolean initiallyEmpty = mInternalMap.isEmpty();
1994e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mInternalViewMap.put(id, info);
2004e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mInternalMap.put(id, info.mHeader.conversation);
2014e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mConversationUriToIdMap.put(info.mHeader.conversation.uri.toString(), id);
2024e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook
2034e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
2044e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            dispatchOnChange(observersCopy);
2054e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            if (initiallyEmpty) {
2064e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                dispatchOnBecomeUnempty(observersCopy);
2074e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
2084a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        }
2094a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
2104a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
2114a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    /** @see java.util.HashMap#remove */
2124e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void remove(Long id) {
2134e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2144e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            removeAll(Collections.singleton(id));
2154e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
216a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook    }
217a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
2184e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void removeAll(Collection<Long> ids) {
2194e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2204e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final boolean initiallyNotEmpty = !mInternalMap.isEmpty();
221866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
2224e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final BiMap<Long, String> inverseMap = mConversationUriToIdMap.inverse();
223fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook
2244e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            for (Long id : ids) {
2254e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                mInternalViewMap.remove(id);
2264e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                mInternalMap.remove(id);
2274e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                inverseMap.remove(id);
2284e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
2294a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
2304e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
2314e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            dispatchOnChange(observersCopy);
2324e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            if (mInternalMap.isEmpty() && initiallyNotEmpty) {
2334e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                dispatchOnEmpty(observersCopy);
2344e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
2354a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        }
2364a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
2374a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
2381ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    /**
2391ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     * Unregisters an observer for change events.
2401ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     *
2411ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     * @param observer the observer to unregister.
2421ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     */
2434e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public void removeObserver(ConversationSetObserver observer) {
2444e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2454e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mObservers.remove(observer);
2464e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
2474a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
2484a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
249d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    /**
250d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * Returns the number of conversations that are currently selected
251d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * @return the number of selected conversations.
252d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     */
2534e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public int size() {
2544e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2554e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return mInternalMap.size();
2564e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
2574a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
2584a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
2594a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    /**
260d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * Toggles the existence of the given conversation in the selection set. If the conversation is
261d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * currently selected, it is deselected. If it doesn't exist in the selection set, then it is
26272ef2e7ce576ad13ce6179a6a8c74f8ecad9ae85Vikram Aggarwal     * selected. If you are certain that you are deselecting a conversation (you have verified
26372ef2e7ce576ad13ce6179a6a8c74f8ecad9ae85Vikram Aggarwal     * that {@link #contains(Conversation)} or {@link #containsKey(Long)} are true), then you
26472ef2e7ce576ad13ce6179a6a8c74f8ecad9ae85Vikram Aggarwal     * may pass a null {@link ConversationItemView}.
2651ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     * @param conversation
2664a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal     */
267866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    public void toggle(ConversationItemView view, Conversation conversation) {
2681ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        long conversationId = conversation.id;
2691ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        if (containsKey(conversationId)) {
27072ef2e7ce576ad13ce6179a6a8c74f8ecad9ae85Vikram Aggarwal            // We must not do anything with view here.
2711ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal            remove(conversationId);
2724a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        } else {
273866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira            put(conversationId, view);
2744a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        }
2754a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
2764a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
2771ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    /** @see java.util.HashMap#values */
2784e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public Collection<Conversation> values() {
2794e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2804e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return mInternalMap.values();
2814e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
2824a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
2834a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
284a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook    /** @see java.util.HashMap#keySet() */
2854e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public Set<Long> keySet() {
2864e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2874e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return mInternalMap.keySet();
2884e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
289a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook    }
290a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
291cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal    /**
292cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal     * Puts all conversations given in the input argument into the selection set. If there are
293cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal     * any listeners they are notified once after adding <em>all</em> conversations to the selection
294cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal     * set.
295cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal     * @see java.util.HashMap#putAll(java.util.Map)
296cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal     */
2974556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang    public void putAll(ConversationSelectionSet other) {
2984556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        if (other == null) {
2994556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang            return;
3004556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        }
3014556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang
3024556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        final boolean initiallyEmpty = mInternalMap.isEmpty();
3034556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        mInternalMap.putAll(other.mInternalMap);
3044556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang
3054e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        final Set<Long> keys = other.mInternalMap.keySet();
3064e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        for (Long key : keys) {
3074e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // Fill out the view map with null. The sizes will match, but
3084e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // we won't have any views available yet to store.
3094e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mInternalViewMap.put(key, null);
3104e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
3114e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook
3124556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
3134556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        dispatchOnChange(observersCopy);
3144556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        if (initiallyEmpty) {
3154556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang            dispatchOnBecomeUnempty(observersCopy);
3164556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        }
3174556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang    }
3184556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang
3194a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    @Override
3204a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    public void writeToParcel(Parcel dest, int flags) {
3211ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        Conversation[] values = values().toArray(new Conversation[size()]);
3224a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        dest.writeParcelableArray(values, flags);
3234a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
324866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
325866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    public Collection<ConversationItemView> views() {
326866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        return mInternalViewMap.values();
327866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    }
32804dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal
32904dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal    /**
33004dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal     * @param deletedRows an arraylist of conversation IDs which have been deleted.
33104dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal     */
33204dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal    public void delete(ArrayList<Integer> deletedRows) {
33304dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal        for (long id : deletedRows) {
33404dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal            remove(id);
33504dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal        }
33604dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal    }
337a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
338a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook    /**
339a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook     * Iterates through a cursor of conversations and ensures that the current set is present
340a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook     * within the result set denoted by the cursor. Any conversations not foun in the result set
341a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook     * is removed from the collection.
342a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook     */
3434e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public void validateAgainstCursor(ConversationCursor cursor) {
3444e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
3454e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            if (isEmpty()) {
3464e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                return;
3474e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
348a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
3494e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            if (cursor == null) {
3504e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                clear();
3514e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                return;
3524e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
353a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
3544e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // First ask the ConversationCursor for the list of conversations that have been deleted
3554e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final Set<String> deletedConversations = cursor.getDeletedItems();
3564e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // For each of the uris in the deleted set, add the conversation id to the
3574e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // itemsToRemoveFromBatch set.
3584e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final Set<Long> itemsToRemoveFromBatch = Sets.newHashSet();
3594e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            for (String conversationUri : deletedConversations) {
3604e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                final Long conversationId = mConversationUriToIdMap.get(conversationUri);
3614e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                if (conversationId != null) {
3624e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                    itemsToRemoveFromBatch.add(conversationId);
3634e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                }
364fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook            }
365fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook
3664e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // Get the set of the items that had been in the batch
3674e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final Set<Long> batchConversationToCheck = new HashSet<Long>(keySet());
3686b872ca329daa3ab9704731459d9f10c159a8298Paul Westbrook
3694e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // Remove all of the items that we know are missing.  This will leave the items where
3704e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // we need to check for existence in the cursor
3714e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            batchConversationToCheck.removeAll(itemsToRemoveFromBatch);
3726b872ca329daa3ab9704731459d9f10c159a8298Paul Westbrook
3730a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook            // While there are items to check, remove all items that are still in the cursor.
3740a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook            final Set<Long> cursorConversationIds = cursor.getConversationIds();
3750a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook            while (!batchConversationToCheck.isEmpty() && cursorConversationIds != null) {
3760a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook                batchConversationToCheck.removeAll(cursorConversationIds);
3774e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
3784e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook
3794e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // At this point any of the item that are remaining in the batchConversationToCheck set
3804e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // are to be removed from the selected conversation set
3814e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            itemsToRemoveFromBatch.addAll(batchConversationToCheck);
382a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
3834e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            removeAll(itemsToRemoveFromBatch);
3844e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
3854e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    }
386a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
3874e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    @Override
3884e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public String toString() {
3894e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
3904e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return String.format("%s:%s", super.toString(), mInternalMap);
39144c1571a6a81eda7dd508f74b64adae1e124101bPaul Westbrook        }
39244c1571a6a81eda7dd508f74b64adae1e124101bPaul Westbrook    }
3934a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal}
394