107a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee/* 207a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee * Copyright (C) 2015 The Android Open Source Project 307a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee * 407a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee * Licensed under the Apache License, Version 2.0 (the "License"); 507a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee * you may not use this file except in compliance with the License. 607a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee * You may obtain a copy of the License at 707a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee * 807a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee * http://www.apache.org/licenses/LICENSE-2.0 907a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee * 1007a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee * Unless required by applicable law or agreed to in writing, software 1107a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee * distributed under the License is distributed on an "AS IS" BASIS, 1207a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1307a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee * See the License for the specific language governing permissions and 1407a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee * limitations under the License. 1507a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee */ 1607a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 1707a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Leepackage com.android.dialer.calllog; 1807a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 1907a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Leeimport android.content.Context; 2007a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Leeimport android.database.ContentObserver; 2107a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Leeimport android.database.Cursor; 2207a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Leeimport android.database.DataSetObserver; 2307a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Leeimport android.os.Handler; 24492cd7371c6d8fddc4de85887a4ed9a89d602767Andrew Leeimport android.support.v7.widget.RecyclerView; 2507a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Leeimport android.util.SparseIntArray; 2607a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 2707a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee/** 280599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee * Maintains a list that groups items into groups of consecutive elements which are disjoint, 290599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee * that is, an item can only belong to one group. This is leveraged for grouping calls in the 300599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee * call log received from or made to the same phone number. 3107a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee * 320599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee * There are two integers stored as metadata for every list item in the adapter. 3307a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee */ 34492cd7371c6d8fddc4de85887a4ed9a89d602767Andrew Leeabstract class GroupingListAdapter extends RecyclerView.Adapter { 3507a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 3607a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee private Context mContext; 3707a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee private Cursor mCursor; 3807a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 3907a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee /** 400599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee * SparseIntArray, which maps the cursor position of the first element of a group to the size 410599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee * of the group. The index of a key in this map corresponds to the list position of that group. 4207a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee */ 430599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee private SparseIntArray mGroupMetadata; 440599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee private int mItemCount; 4507a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 4607a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee protected ContentObserver mChangeObserver = new ContentObserver(new Handler()) { 4707a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee @Override 4807a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee public boolean deliverSelfNotifications() { 4907a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee return true; 5007a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } 5107a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 5207a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee @Override 5307a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee public void onChange(boolean selfChange) { 5407a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee onContentChanged(); 5507a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } 5607a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee }; 5707a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 5807a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee protected DataSetObserver mDataSetObserver = new DataSetObserver() { 5907a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee @Override 6007a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee public void onChanged() { 6107a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee notifyDataSetChanged(); 6207a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } 6307a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee }; 6407a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 6507a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee public GroupingListAdapter(Context context) { 6607a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee mContext = context; 670599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee reset(); 6807a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } 6907a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 7007a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee /** 7107a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee * Finds all groups of adjacent items in the cursor and calls {@link #addGroup} for 7207a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee * each of them. 7307a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee */ 7407a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee protected abstract void addGroups(Cursor cursor); 7507a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 766b0056f348e1f9ecf0f91b53b51d240961bc8313Sarmad Hashmi protected abstract void addVoicemailGroups(Cursor cursor); 776b0056f348e1f9ecf0f91b53b51d240961bc8313Sarmad Hashmi 78492cd7371c6d8fddc4de85887a4ed9a89d602767Andrew Lee protected abstract void onContentChanged(); 7907a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 8007a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee public void changeCursor(Cursor cursor) { 816b0056f348e1f9ecf0f91b53b51d240961bc8313Sarmad Hashmi changeCursor(cursor, false); 826b0056f348e1f9ecf0f91b53b51d240961bc8313Sarmad Hashmi } 836b0056f348e1f9ecf0f91b53b51d240961bc8313Sarmad Hashmi 846b0056f348e1f9ecf0f91b53b51d240961bc8313Sarmad Hashmi public void changeCursorVoicemail(Cursor cursor) { 856b0056f348e1f9ecf0f91b53b51d240961bc8313Sarmad Hashmi changeCursor(cursor, true); 866b0056f348e1f9ecf0f91b53b51d240961bc8313Sarmad Hashmi } 876b0056f348e1f9ecf0f91b53b51d240961bc8313Sarmad Hashmi 886b0056f348e1f9ecf0f91b53b51d240961bc8313Sarmad Hashmi public void changeCursor(Cursor cursor, boolean voicemail) { 8907a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee if (cursor == mCursor) { 9007a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee return; 9107a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } 9207a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 9307a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee if (mCursor != null) { 9407a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee mCursor.unregisterContentObserver(mChangeObserver); 9507a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee mCursor.unregisterDataSetObserver(mDataSetObserver); 9607a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee mCursor.close(); 9707a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } 980599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee 990599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee // Reset whenever the cursor is changed. 1000599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee reset(); 10107a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee mCursor = cursor; 10207a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 10307a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee if (cursor != null) { 1046b0056f348e1f9ecf0f91b53b51d240961bc8313Sarmad Hashmi if (voicemail) { 1056b0056f348e1f9ecf0f91b53b51d240961bc8313Sarmad Hashmi addVoicemailGroups(mCursor); 1066b0056f348e1f9ecf0f91b53b51d240961bc8313Sarmad Hashmi } else { 1076b0056f348e1f9ecf0f91b53b51d240961bc8313Sarmad Hashmi addGroups(mCursor); 1086b0056f348e1f9ecf0f91b53b51d240961bc8313Sarmad Hashmi } 1090599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee 1100599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee // Calculate the item count by subtracting group child counts from the cursor count. 1110599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee mItemCount = mGroupMetadata.size(); 1120599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee 11307a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee cursor.registerContentObserver(mChangeObserver); 11407a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee cursor.registerDataSetObserver(mDataSetObserver); 11507a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee notifyDataSetChanged(); 11607a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } 11707a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } 11807a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 11907a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee /** 1200599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee * Records information about grouping in the list. 1210599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee * Should be called by the overridden {@link #addGroups} method. 12207a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee */ 1230599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee public void addGroup(int cursorPosition, int groupSize) { 1240599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee int lastIndex = mGroupMetadata.size() - 1; 1250599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee if (lastIndex < 0 || cursorPosition <= mGroupMetadata.keyAt(lastIndex)) { 1260599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee mGroupMetadata.put(cursorPosition, groupSize); 1270599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee } else { 1280599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee // Optimization to avoid binary search if adding groups in ascending cursor position. 1290599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee mGroupMetadata.append(cursorPosition, groupSize); 13007a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } 13107a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } 13207a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 133492cd7371c6d8fddc4de85887a4ed9a89d602767Andrew Lee @Override 134492cd7371c6d8fddc4de85887a4ed9a89d602767Andrew Lee public int getItemCount() { 1350599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee return mItemCount; 13607a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } 13707a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 13807a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee /** 1390599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee * Given the position of a list item, returns the size of the group of items corresponding to 1400599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee * that position. 14107a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee */ 1420599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee public int getGroupSize(int listPosition) { 143ab90a18e2f05c7ae4158b770a9fbe9cbe4544597Andrew Lee if (listPosition < 0 || listPosition >= mGroupMetadata.size()) { 1440599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee return 0; 14507a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } 14607a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 1470599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee return mGroupMetadata.valueAt(listPosition); 14807a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } 14907a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 15007a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee /** 1510599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee * Given the position of a list item, returns the the first item in the group of items 1520599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee * corresponding to that position. 15307a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee */ 1540599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee public Object getItem(int listPosition) { 155ab90a18e2f05c7ae4158b770a9fbe9cbe4544597Andrew Lee if (mCursor == null || listPosition < 0 || listPosition >= mGroupMetadata.size()) { 15607a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee return null; 15707a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } 15807a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 1590599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee int cursorPosition = mGroupMetadata.keyAt(listPosition); 1600599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee if (mCursor.moveToPosition(cursorPosition)) { 16107a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee return mCursor; 16207a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } else { 16307a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee return null; 16407a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } 16507a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } 16607a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee 1670599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee private void reset() { 1680599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee mItemCount = 0; 1690599e880630892becf1a1991e6712e0e6d9df3c8Andrew Lee mGroupMetadata = new SparseIntArray(); 17007a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee } 17107a5e0a68e95bc7e8098bb018004910dc97fa608Andrew Lee} 172