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