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;
241ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwalimport com.android.mail.providers.Conversation;
252bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huangimport com.google.common.annotations.VisibleForTesting;
262bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huangimport com.google.common.collect.BiMap;
272bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huangimport com.google.common.collect.HashBiMap;
282bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huangimport com.google.common.collect.Lists;
292bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huangimport com.google.common.collect.Sets;
30a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
314a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalimport java.util.ArrayList;
324a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalimport java.util.Collection;
33a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrookimport java.util.Collections;
344a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalimport java.util.HashMap;
35a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrookimport java.util.HashSet;
36a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrookimport java.util.Set;
374a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
384a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal/**
394a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal * A simple thread-safe wrapper over a set of conversations representing a
404a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal * selection set (e.g. in a conversation list). This class dispatches changes
414a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal * when the set goes empty, and when it becomes unempty. For simplicity, this
424a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal * class <b>does not allow modifications</b> to the collection in observers when
434a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal * responding to change events.
444a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal */
454a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwalpublic class ConversationSelectionSet implements Parcelable {
462bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang    public static final ClassLoaderCreator<ConversationSelectionSet> CREATOR =
472bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang            new ClassLoaderCreator<ConversationSelectionSet>() {
481ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
491ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        @Override
501ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        public ConversationSelectionSet createFromParcel(Parcel source) {
512bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang            return new ConversationSelectionSet(source, null);
522bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang        }
532bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang
542bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang        @Override
552bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang        public ConversationSelectionSet createFromParcel(Parcel source, ClassLoader loader) {
562bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang            return new ConversationSelectionSet(source, loader);
571ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        }
581ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
591ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        @Override
601ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        public ConversationSelectionSet[] newArray(int size) {
611ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal            return new ConversationSelectionSet[size];
621ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        }
632bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang
641ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    };
651ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
664e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private final Object mLock = new Object();
67ab1b5b6d16800ff675116cdab33cf26ab3514732Vikram Aggarwal    /** Map of conversation ID to conversation objects. Every selected conversation is here. */
68ab1b5b6d16800ff675116cdab33cf26ab3514732Vikram Aggarwal    private final HashMap<Long, Conversation> mInternalMap = new HashMap<Long, Conversation>();
69ab1b5b6d16800ff675116cdab33cf26ab3514732Vikram Aggarwal    /** Map of Conversation URI to Conversation ID. */
70fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook    private final BiMap<String, Long> mConversationUriToIdMap = HashBiMap.create();
71ab1b5b6d16800ff675116cdab33cf26ab3514732Vikram Aggarwal    /** All objects that are interested in changes to the selected set. */
724a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    @VisibleForTesting
73f4ff7675cbc23c56b9b35f9f26574afef030f0afMark Wei    final Set<ConversationSetObserver> mObservers = new HashSet<ConversationSetObserver>();
744a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
75ab1b5b6d16800ff675116cdab33cf26ab3514732Vikram Aggarwal    /**
76ab1b5b6d16800ff675116cdab33cf26ab3514732Vikram Aggarwal     * Create a new object,
77ab1b5b6d16800ff675116cdab33cf26ab3514732Vikram Aggarwal     */
782bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang    public ConversationSelectionSet() {
79ab1b5b6d16800ff675116cdab33cf26ab3514732Vikram Aggarwal        // Do nothing.
802bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang    }
812bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang
822bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang    private ConversationSelectionSet(Parcel source, ClassLoader loader) {
832bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang        Parcelable[] conversations = source.readParcelableArray(loader);
842bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang        for (Parcelable parceled : conversations) {
852bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang            Conversation conversation = (Conversation) parceled;
862bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang            put(conversation.id, conversation);
872bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang        }
882bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang    }
892bc8bc1f69faacaddab41ab61e1d2e8658f4181fAndy Huang
904a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    /**
914a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal     * Registers an observer to listen for interesting changes on this set.
924a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal     *
934a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal     * @param observer the observer to register.
944a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal     */
954e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public void addObserver(ConversationSetObserver observer) {
964e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
974e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mObservers.add(observer);
984e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
994a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
1004a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
101d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    /**
102d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * Clear the selected set entirely.
103d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     */
1044e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public void clear() {
1054e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1064e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            boolean initiallyNotEmpty = !mInternalMap.isEmpty();
1074e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mInternalMap.clear();
1084e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mConversationUriToIdMap.clear();
1094e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook
1104e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            if (mInternalMap.isEmpty() && initiallyNotEmpty) {
1114e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
1124e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                dispatchOnChange(observersCopy);
1134e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                dispatchOnEmpty(observersCopy);
1144e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
1151ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        }
1161ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    }
1171ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
118d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    /**
119d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * Returns true if the given key exists in the conversation selection set. This assumes
120d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * the internal representation holds conversation.id values.
121d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * @param key the id of the conversation
122d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * @return true if the key exists in this selected set.
123d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     */
12420a97ded277fdcd3c63952a23718410c2882103fVikram Aggarwal    private boolean containsKey(Long key) {
1254e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1264e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return mInternalMap.containsKey(key);
1274e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
1281ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    }
1291ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
130d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    /**
131d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * Returns true if the given conversation is stored in the selection set.
132d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * @param conversation
133d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * @return true if the conversation exists in the selected set.
134d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     */
1354e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public boolean contains(Conversation conversation) {
1364e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1374e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return containsKey(conversation.id);
1384e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
139d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    }
140d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal
1411ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    @Override
1421ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    public int describeContents() {
1431ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        return 0;
1441ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    }
1451ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
1464e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void dispatchOnBecomeUnempty(ArrayList<ConversationSetObserver> observers) {
1474e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1484e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            for (ConversationSetObserver observer : observers) {
1494e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                observer.onSetPopulated(this);
1504e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
1511ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        }
1524a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
1534a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
1544e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void dispatchOnChange(ArrayList<ConversationSetObserver> observers) {
1554e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1564e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // Copy observers so that they may unregister themselves as listeners on
1574e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // event handling.
1584e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            for (ConversationSetObserver observer : observers) {
1594e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                observer.onSetChanged(this);
1604e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
1614a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        }
1624a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
1634a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
1644e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void dispatchOnEmpty(ArrayList<ConversationSetObserver> observers) {
1654e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1664e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            for (ConversationSetObserver observer : observers) {
1674e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                observer.onSetEmpty();
1684e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
1694a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        }
1704a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
1714a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
1721ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    /**
1731ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     * Is this conversation set empty?
1741ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     * @return true if the conversation selection set is empty. False otherwise.
1751ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     */
1764e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public boolean isEmpty() {
1774e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1784e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return mInternalMap.isEmpty();
1794e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
1801ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    }
1811ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal
1824e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void put(Long id, Conversation info) {
1834e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1844e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final boolean initiallyEmpty = mInternalMap.isEmpty();
1854e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mInternalMap.put(id, info);
1864e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mConversationUriToIdMap.put(info.uri.toString(), id);
187866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
1884e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
1894e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            dispatchOnChange(observersCopy);
1904e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            if (initiallyEmpty) {
1914e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                dispatchOnBecomeUnempty(observersCopy);
1924e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
193866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        }
194866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    }
195866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
1964a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    /** @see java.util.HashMap#remove */
1974e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void remove(Long id) {
1984e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
1994e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            removeAll(Collections.singleton(id));
2004e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
201a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook    }
202a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
2034e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    private void removeAll(Collection<Long> ids) {
2044e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2054e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final boolean initiallyNotEmpty = !mInternalMap.isEmpty();
206866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
2074e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final BiMap<Long, String> inverseMap = mConversationUriToIdMap.inverse();
208fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook
2094e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            for (Long id : ids) {
2104e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                mInternalMap.remove(id);
2114e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                inverseMap.remove(id);
2124e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
2134a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
2144e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
2154e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            dispatchOnChange(observersCopy);
2164e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            if (mInternalMap.isEmpty() && initiallyNotEmpty) {
2174e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                dispatchOnEmpty(observersCopy);
2184e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
2194a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        }
2204a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
2214a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
2221ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    /**
2231ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     * Unregisters an observer for change events.
2241ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     *
2251ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     * @param observer the observer to unregister.
2261ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     */
2274e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public void removeObserver(ConversationSetObserver observer) {
2284e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2294e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            mObservers.remove(observer);
2304e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
2314a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
2324a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
233d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    /**
234d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * Returns the number of conversations that are currently selected
235d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * @return the number of selected conversations.
236d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     */
2374e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public int size() {
2384e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2394e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return mInternalMap.size();
2404e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
2414a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
2424a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
2434a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    /**
244d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * Toggles the existence of the given conversation in the selection set. If the conversation is
245d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal     * currently selected, it is deselected. If it doesn't exist in the selection set, then it is
24620a97ded277fdcd3c63952a23718410c2882103fVikram Aggarwal     * selected.
2471ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal     * @param conversation
2484a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal     */
24920a97ded277fdcd3c63952a23718410c2882103fVikram Aggarwal    public void toggle(Conversation conversation) {
25020a97ded277fdcd3c63952a23718410c2882103fVikram Aggarwal        final long conversationId = conversation.id;
2511ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        if (containsKey(conversationId)) {
25272ef2e7ce576ad13ce6179a6a8c74f8ecad9ae85Vikram Aggarwal            // We must not do anything with view here.
2531ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal            remove(conversationId);
2544a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        } else {
25520a97ded277fdcd3c63952a23718410c2882103fVikram Aggarwal            put(conversationId, conversation);
2564a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        }
2574a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
2584a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
2591ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal    /** @see java.util.HashMap#values */
2604e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public Collection<Conversation> values() {
2614e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2624e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return mInternalMap.values();
2634e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
2644a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
2654a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal
266a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook    /** @see java.util.HashMap#keySet() */
2674e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public Set<Long> keySet() {
2684e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
2694e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return mInternalMap.keySet();
2704e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
271a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook    }
272a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
273cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal    /**
274cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal     * Puts all conversations given in the input argument into the selection set. If there are
275cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal     * any listeners they are notified once after adding <em>all</em> conversations to the selection
276cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal     * set.
277cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal     * @see java.util.HashMap#putAll(java.util.Map)
278cabd3f227bf97159774e246ad278bb49d4aa2badVikram Aggarwal     */
2794556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang    public void putAll(ConversationSelectionSet other) {
2804556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        if (other == null) {
2814556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang            return;
2824556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        }
2834556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang
2844556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        final boolean initiallyEmpty = mInternalMap.isEmpty();
2854556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        mInternalMap.putAll(other.mInternalMap);
2864556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang
287669947bf67a3d0eed377b18099bb873604ad5009Vikram Aggarwal        final ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
2884556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        dispatchOnChange(observersCopy);
2894556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        if (initiallyEmpty) {
2904556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang            dispatchOnBecomeUnempty(observersCopy);
2914556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang        }
2924556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang    }
2934556a44bef944b90e140d2ba67cc723e0ad5da3aAndy Huang
2944a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    @Override
2954a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    public void writeToParcel(Parcel dest, int flags) {
2961ddcf0f2bf44d3c9db89112ef52510d9b2433ac4Vikram Aggarwal        Conversation[] values = values().toArray(new Conversation[size()]);
2974a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal        dest.writeParcelableArray(values, flags);
2984a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal    }
299866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
30004dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal    /**
30104dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal     * @param deletedRows an arraylist of conversation IDs which have been deleted.
30204dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal     */
30304dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal    public void delete(ArrayList<Integer> deletedRows) {
30404dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal        for (long id : deletedRows) {
30504dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal            remove(id);
30604dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal        }
30704dc819886abc2e4f56e644b03449e29cdbae2d0Vikram Aggarwal    }
308a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
309a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook    /**
310a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook     * Iterates through a cursor of conversations and ensures that the current set is present
311a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook     * within the result set denoted by the cursor. Any conversations not foun in the result set
312a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook     * is removed from the collection.
313a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook     */
3144e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public void validateAgainstCursor(ConversationCursor cursor) {
3154e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
3164e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            if (isEmpty()) {
3174e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                return;
3184e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
319a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
3204e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            if (cursor == null) {
3214e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                clear();
3224e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                return;
3234e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
324a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
3254e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // First ask the ConversationCursor for the list of conversations that have been deleted
3264e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final Set<String> deletedConversations = cursor.getDeletedItems();
3274e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // For each of the uris in the deleted set, add the conversation id to the
3284e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // itemsToRemoveFromBatch set.
3294e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final Set<Long> itemsToRemoveFromBatch = Sets.newHashSet();
3304e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            for (String conversationUri : deletedConversations) {
3314e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                final Long conversationId = mConversationUriToIdMap.get(conversationUri);
3324e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                if (conversationId != null) {
3334e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                    itemsToRemoveFromBatch.add(conversationId);
3344e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook                }
335fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook            }
336fe7f5bb113014365746c9e1368773c5448645a9dPaul Westbrook
3374e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // Get the set of the items that had been in the batch
3384e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            final Set<Long> batchConversationToCheck = new HashSet<Long>(keySet());
3396b872ca329daa3ab9704731459d9f10c159a8298Paul Westbrook
3404e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // Remove all of the items that we know are missing.  This will leave the items where
3414e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // we need to check for existence in the cursor
3424e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            batchConversationToCheck.removeAll(itemsToRemoveFromBatch);
343fa322a52f9d90e6cb8db9a4895eaace2de8de9a8Paul Westbrook            // At this point batchConversationToCheck contains the conversation ids for the
344fa322a52f9d90e6cb8db9a4895eaace2de8de9a8Paul Westbrook            // conversations that had been in the batch selection, with the items we know have been
345fa322a52f9d90e6cb8db9a4895eaace2de8de9a8Paul Westbrook            // deleted removed.
3466b872ca329daa3ab9704731459d9f10c159a8298Paul Westbrook
347fa322a52f9d90e6cb8db9a4895eaace2de8de9a8Paul Westbrook            // This set contains the conversation ids that are in the conversation cursor
3480a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook            final Set<Long> cursorConversationIds = cursor.getConversationIds();
349fa322a52f9d90e6cb8db9a4895eaace2de8de9a8Paul Westbrook
350fa322a52f9d90e6cb8db9a4895eaace2de8de9a8Paul Westbrook            // We want to remove all of the valid items that are in the conversation cursor, from
351fa322a52f9d90e6cb8db9a4895eaace2de8de9a8Paul Westbrook            // the batchConversations to check.  The goal is after this block, anything remaining
352fa322a52f9d90e6cb8db9a4895eaace2de8de9a8Paul Westbrook            // would be items that don't exist in the conversation cursor anymore.
353fa322a52f9d90e6cb8db9a4895eaace2de8de9a8Paul Westbrook            if (!batchConversationToCheck.isEmpty() && cursorConversationIds != null) {
3540a22d4482396f3717b36796e594d5f8e9760d509Paul Westbrook                batchConversationToCheck.removeAll(cursorConversationIds);
3554e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            }
3564e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook
3574e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // At this point any of the item that are remaining in the batchConversationToCheck set
3584e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            // are to be removed from the selected conversation set
3594e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            itemsToRemoveFromBatch.addAll(batchConversationToCheck);
360a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
3614e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            removeAll(itemsToRemoveFromBatch);
3624e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        }
3634e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    }
364a13b3742522987f768ef9a1a1cddd32ff8105f0ePaul Westbrook
3654e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    @Override
3664e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook    public String toString() {
3674e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook        synchronized (mLock) {
3684e22a2ad8e7b70d71710e5dc1d209320fe71929dPaul Westbrook            return String.format("%s:%s", super.toString(), mInternalMap);
36944c1571a6a81eda7dd508f74b64adae1e124101bPaul Westbrook        }
37044c1571a6a81eda7dd508f74b64adae1e124101bPaul Westbrook    }
3714a5c530b0a67e22bd74df8f10f29278dc8d86459Vikram Aggarwal}
372