ConversationSelectionSet.java revision 2bc8bc1f69faacaddab41ab61e1d2e8658f4181f
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 android.os.Parcel;
214a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalimport android.os.Parcelable;
224a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
23a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrookimport com.android.mail.browse.ConversationCursor;
242bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huangimport com.android.mail.browse.ConversationItemView;
251ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwalimport com.android.mail.providers.Conversation;
262bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huangimport com.google.common.annotations.VisibleForTesting;
272bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huangimport com.google.common.collect.BiMap;
282bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huangimport com.google.common.collect.HashBiMap;
292bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huangimport com.google.common.collect.Lists;
302bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huangimport com.google.common.collect.Sets;
31a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
324a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalimport java.util.ArrayList;
334a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalimport java.util.Collection;
34a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrookimport java.util.Collections;
354a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalimport java.util.HashMap;
36a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrookimport java.util.HashSet;
37a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrookimport java.util.Set;
384a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
394a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal/**
404a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal * A simple thread-safe wrapper over a set of conversations representing a
414a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal * selection set (e.g. in a conversation list). This class dispatches changes
424a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal * when the set goes empty, and when it becomes unempty. For simplicity, this
434a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal * class <b>does not allow modifications</b> to the collection in observers when
444a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal * responding to change events.
454a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal */
464a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalpublic class ConversationSelectionSet implements Parcelable {
472bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang    public static final ClassLoaderCreator<ConversationSelectionSet> CREATOR =
482bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang            new ClassLoaderCreator<ConversationSelectionSet>() {
491ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
501ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        @Override
511ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        public ConversationSelectionSet createFromParcel(Parcel source) {
522bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang            return new ConversationSelectionSet(source, null);
532bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang        }
542bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang
552bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang        @Override
562bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang        public ConversationSelectionSet createFromParcel(Parcel source, ClassLoader loader) {
572bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang            return new ConversationSelectionSet(source, loader);
581ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        }
591ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
601ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        @Override
611ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        public ConversationSelectionSet[] newArray(int size) {
621ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal            return new ConversationSelectionSet[size];
631ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        }
642bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang
651ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    };
661ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
674e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private final Object mLock = new Object();
681ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    private final HashMap<Long, Conversation> mInternalMap =
691ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal            new HashMap<Long, Conversation>();
704a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
71c7e3a7db9ff9cb43241995001462764b70b75e96Vikram Aggarwal    /**
72c7e3a7db9ff9cb43241995001462764b70b75e96Vikram Aggarwal     * Map of conversation IDs to {@link ConversationItemView} objects. The views are <b>not</b>
73c7e3a7db9ff9cb43241995001462764b70b75e96Vikram Aggarwal     * updated when a new list view object is created on orientation change.
74c7e3a7db9ff9cb43241995001462764b70b75e96Vikram Aggarwal     */
75866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    private final HashMap<Long, ConversationItemView> mInternalViewMap =
76866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira            new HashMap<Long, ConversationItemView>();
77fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook    private final BiMap<String, Long> mConversationUriToIdMap = HashBiMap.create();
78866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
794a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    @VisibleForTesting
804a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    final ArrayList<ConversationSetObserver> mObservers = new ArrayList<ConversationSetObserver>();
814a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
822bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang    public ConversationSelectionSet() {
832bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang    }
842bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang
852bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang    private ConversationSelectionSet(Parcel source, ClassLoader loader) {
862bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang        Parcelable[] conversations = source.readParcelableArray(loader);
872bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang        for (Parcelable parceled : conversations) {
882bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang            Conversation conversation = (Conversation) parceled;
892bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang            put(conversation.id, conversation);
902bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang        }
912bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang    }
922bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang
934a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    /**
944a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal     * Registers an observer to listen for interesting changes on this set.
954a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal     *
964a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal     * @param observer the observer to register.
974a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal     */
984e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public void addObserver(ConversationSetObserver observer) {
994e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1004e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mObservers.add(observer);
1014e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
1024a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
1034a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
104d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    /**
105d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * Clear the selected set entirely.
106d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     */
1074e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public void clear() {
1084e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1094e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            boolean initiallyNotEmpty = !mInternalMap.isEmpty();
1104e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mInternalViewMap.clear();
1114e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mInternalMap.clear();
1124e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mConversationUriToIdMap.clear();
1134e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook
1144e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            if (mInternalMap.isEmpty() && initiallyNotEmpty) {
1154e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
1164e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                dispatchOnChange(observersCopy);
1174e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                dispatchOnEmpty(observersCopy);
1184e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
1191ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        }
1201ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    }
1211ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
122d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    /**
123d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * Returns true if the given key exists in the conversation selection set. This assumes
124d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * the internal representation holds conversation.id values.
125d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * @param key the id of the conversation
126d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * @return true if the key exists in this selected set.
127d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     */
1284e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public boolean containsKey(Long key) {
1294e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1304e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return mInternalMap.containsKey(key);
1314e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
1321ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    }
1331ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
134d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    /**
135d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * Returns true if the given conversation is stored in the selection set.
136d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * @param conversation
137d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * @return true if the conversation exists in the selected set.
138d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     */
1394e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public boolean contains(Conversation conversation) {
1404e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1414e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return containsKey(conversation.id);
1424e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
143d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    }
144d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal
1451ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    @Override
1461ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    public int describeContents() {
1471ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        return 0;
1481ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    }
1491ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
1504e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void dispatchOnBecomeUnempty(ArrayList<ConversationSetObserver> observers) {
1514e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1524e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            for (ConversationSetObserver observer : observers) {
1534e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                observer.onSetPopulated(this);
1544e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
1551ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        }
1564a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
1574a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
1584e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void dispatchOnChange(ArrayList<ConversationSetObserver> observers) {
1594e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1604e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // Copy observers so that they may unregister themselves as listeners on
1614e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // event handling.
1624e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            for (ConversationSetObserver observer : observers) {
1634e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                observer.onSetChanged(this);
1644e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
1654a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        }
1664a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
1674a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
1684e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void dispatchOnEmpty(ArrayList<ConversationSetObserver> observers) {
1694e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1704e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            for (ConversationSetObserver observer : observers) {
1714e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                observer.onSetEmpty();
1724e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
1734a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        }
1744a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
1754a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
1761ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    /**
1771ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     * Is this conversation set empty?
1781ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     * @return true if the conversation selection set is empty. False otherwise.
1791ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     */
1804e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public boolean isEmpty() {
1814e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1824e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return mInternalMap.isEmpty();
1834e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
1841ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    }
1851ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
1864e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void put(Long id, Conversation info) {
1874e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1884e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final boolean initiallyEmpty = mInternalMap.isEmpty();
1894e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mInternalMap.put(id, info);
1904e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // Fill out the view map with null. The sizes will match, but
1914e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // we won't have any views available yet to store.
1924e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mInternalViewMap.put(id, null);
1934e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mConversationUriToIdMap.put(info.uri.toString(), id);
194866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
1954e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
1964e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            dispatchOnChange(observersCopy);
1974e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            if (initiallyEmpty) {
1984e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                dispatchOnBecomeUnempty(observersCopy);
1994e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
200866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        }
201866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    }
202866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
203866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    /** @see java.util.HashMap#put */
2044e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void put(Long id, ConversationItemView info) {
2054e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2064e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            boolean initiallyEmpty = mInternalMap.isEmpty();
2074e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mInternalViewMap.put(id, info);
2084e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mInternalMap.put(id, info.mHeader.conversation);
2094e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mConversationUriToIdMap.put(info.mHeader.conversation.uri.toString(), id);
2104e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook
2114e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
2124e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            dispatchOnChange(observersCopy);
2134e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            if (initiallyEmpty) {
2144e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                dispatchOnBecomeUnempty(observersCopy);
2154e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
2164a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        }
2174a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
2184a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
2194a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    /** @see java.util.HashMap#remove */
2204e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void remove(Long id) {
2214e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2224e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            removeAll(Collections.singleton(id));
2234e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
224a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook    }
225a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
2264e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void removeAll(Collection<Long> ids) {
2274e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2284e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final boolean initiallyNotEmpty = !mInternalMap.isEmpty();
229866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
2304e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final BiMap<Long, String> inverseMap = mConversationUriToIdMap.inverse();
231fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook
2324e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            for (Long id : ids) {
2334e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                mInternalViewMap.remove(id);
2344e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                mInternalMap.remove(id);
2354e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                inverseMap.remove(id);
2364e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
2374a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
2384e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
2394e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            dispatchOnChange(observersCopy);
2404e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            if (mInternalMap.isEmpty() && initiallyNotEmpty) {
2414e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                dispatchOnEmpty(observersCopy);
2424e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
2434a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        }
2444a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
2454a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
2461ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    /**
2471ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     * Unregisters an observer for change events.
2481ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     *
2491ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     * @param observer the observer to unregister.
2501ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     */
2514e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public void removeObserver(ConversationSetObserver observer) {
2524e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2534e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mObservers.remove(observer);
2544e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
2554a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
2564a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
257d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    /**
258d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * Returns the number of conversations that are currently selected
259d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * @return the number of selected conversations.
260d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     */
2614e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public int size() {
2624e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2634e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return mInternalMap.size();
2644e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
2654a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
2664a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
2674a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    /**
268d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * Toggles the existence of the given conversation in the selection set. If the conversation is
269d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * currently selected, it is deselected. If it doesn't exist in the selection set, then it is
27072ef2e7ce576ad13ce6179a6a8c74f8ecad9ae85Vikram Aggarwal     * selected. If you are certain that you are deselecting a conversation (you have verified
27172ef2e7ce576ad13ce6179a6a8c74f8ecad9ae85Vikram Aggarwal     * that {@link #contains(Conversation)} or {@link #containsKey(Long)} are true), then you
27272ef2e7ce576ad13ce6179a6a8c74f8ecad9ae85Vikram Aggarwal     * may pass a null {@link ConversationItemView}.
2731ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     * @param conversation
2744a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal     */
275866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    public void toggle(ConversationItemView view, Conversation conversation) {
2761ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        long conversationId = conversation.id;
2771ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        if (containsKey(conversationId)) {
27872ef2e7ce576ad13ce6179a6a8c74f8ecad9ae85Vikram Aggarwal            // We must not do anything with view here.
2791ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal            remove(conversationId);
2804a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        } else {
281866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira            put(conversationId, view);
2824a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        }
2834a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
2844a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
2851ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    /** @see java.util.HashMap#values */
2864e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public Collection<Conversation> values() {
2874e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2884e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return mInternalMap.values();
2894e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
2904a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
2914a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
292a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook    /** @see java.util.HashMap#keySet() */
2934e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public Set<Long> keySet() {
2944e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2954e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return mInternalMap.keySet();
2964e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
297a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook    }
298a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
299cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal    /**
300cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal     * Puts all conversations given in the input argument into the selection set. If there are
301cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal     * any listeners they are notified once after adding <em>all</em> conversations to the selection
302cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal     * set.
303cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal     * @see java.util.HashMap#putAll(java.util.Map)
304cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal     */
3054556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang    public void putAll(ConversationSelectionSet other) {
3064556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        if (other == null) {
3074556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang            return;
3084556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        }
3094556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang
3104556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        final boolean initiallyEmpty = mInternalMap.isEmpty();
3114556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        mInternalMap.putAll(other.mInternalMap);
3124556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang
3134e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        final Set<Long> keys = other.mInternalMap.keySet();
3144e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        for (Long key : keys) {
3154e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // Fill out the view map with null. The sizes will match, but
3164e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // we won't have any views available yet to store.
3174e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mInternalViewMap.put(key, null);
3184e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
3194e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook
3204556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
3214556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        dispatchOnChange(observersCopy);
3224556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        if (initiallyEmpty) {
3234556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang            dispatchOnBecomeUnempty(observersCopy);
3244556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        }
3254556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang    }
3264556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang
3274a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    @Override
3284a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    public void writeToParcel(Parcel dest, int flags) {
3291ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        Conversation[] values = values().toArray(new Conversation[size()]);
3304a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        dest.writeParcelableArray(values, flags);
3314a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
332866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
333866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    public Collection<ConversationItemView> views() {
334866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        return mInternalViewMap.values();
335866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    }
33604dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal
33704dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal    /**
33804dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal     * @param deletedRows an arraylist of conversation IDs which have been deleted.
33904dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal     */
34004dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal    public void delete(ArrayList<Integer> deletedRows) {
34104dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal        for (long id : deletedRows) {
34204dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal            remove(id);
34304dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal        }
34404dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal    }
345a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
346a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook    /**
347a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook     * Iterates through a cursor of conversations and ensures that the current set is present
348a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook     * within the result set denoted by the cursor. Any conversations not foun in the result set
349a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook     * is removed from the collection.
350a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook     */
3514e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public void validateAgainstCursor(ConversationCursor cursor) {
3524e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
3534e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            if (isEmpty()) {
3544e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                return;
3554e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
356a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
3574e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            if (cursor == null) {
3584e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                clear();
3594e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                return;
3604e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
361a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
3624e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // First ask the ConversationCursor for the list of conversations that have been deleted
3634e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final Set<String> deletedConversations = cursor.getDeletedItems();
3644e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // For each of the uris in the deleted set, add the conversation id to the
3654e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // itemsToRemoveFromBatch set.
3664e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final Set<Long> itemsToRemoveFromBatch = Sets.newHashSet();
3674e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            for (String conversationUri : deletedConversations) {
3684e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                final Long conversationId = mConversationUriToIdMap.get(conversationUri);
3694e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                if (conversationId != null) {
3704e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                    itemsToRemoveFromBatch.add(conversationId);
3714e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                }
372fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook            }
373fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook
3744e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // Get the set of the items that had been in the batch
3754e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final Set<Long> batchConversationToCheck = new HashSet<Long>(keySet());
3766b872ca329daa3ab9704731459d9f10c159a8298Paul Westbrook
3774e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // Remove all of the items that we know are missing.  This will leave the items where
3784e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // we need to check for existence in the cursor
3794e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            batchConversationToCheck.removeAll(itemsToRemoveFromBatch);
3806b872ca329daa3ab9704731459d9f10c159a8298Paul Westbrook
3810a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook            // While there are items to check, remove all items that are still in the cursor.
3820a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook            final Set<Long> cursorConversationIds = cursor.getConversationIds();
3830a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook            while (!batchConversationToCheck.isEmpty() && cursorConversationIds != null) {
3840a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook                batchConversationToCheck.removeAll(cursorConversationIds);
3854e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
3864e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook
3874e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // At this point any of the item that are remaining in the batchConversationToCheck set
3884e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // are to be removed from the selected conversation set
3894e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            itemsToRemoveFromBatch.addAll(batchConversationToCheck);
390a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
3914e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            removeAll(itemsToRemoveFromBatch);
3924e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
3934e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    }
394a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
3954e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    @Override
3964e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public String toString() {
3974e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
3984e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return String.format("%s:%s", super.toString(), mInternalMap);
39944c1571a6a81eda7dd508f74b64adae1e124101bPaul Westbrook        }
40044c1571a6a81eda7dd508f74b64adae1e124101bPaul Westbrook    }
4014a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal}
402