1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.dialer.app.calllog; 18 19import android.database.ContentObserver; 20import android.database.Cursor; 21import android.database.DataSetObserver; 22import android.os.Handler; 23import android.support.v7.widget.RecyclerView; 24import android.util.SparseIntArray; 25 26/** 27 * Maintains a list that groups items into groups of consecutive elements which are disjoint, that 28 * is, an item can only belong to one group. This is leveraged for grouping calls in the call log 29 * received from or made to the same phone number. 30 * 31 * <p>There are two integers stored as metadata for every list item in the adapter. 32 */ 33abstract class GroupingListAdapter extends RecyclerView.Adapter { 34 35 protected ContentObserver mChangeObserver = 36 new ContentObserver(new Handler()) { 37 @Override 38 public boolean deliverSelfNotifications() { 39 return true; 40 } 41 42 @Override 43 public void onChange(boolean selfChange) { 44 onContentChanged(); 45 } 46 }; 47 protected DataSetObserver mDataSetObserver = 48 new DataSetObserver() { 49 @Override 50 public void onChanged() { 51 notifyDataSetChanged(); 52 } 53 }; 54 private Cursor mCursor; 55 /** 56 * SparseIntArray, which maps the cursor position of the first element of a group to the size of 57 * the group. The index of a key in this map corresponds to the list position of that group. 58 */ 59 private SparseIntArray mGroupMetadata; 60 61 private int mItemCount; 62 63 public GroupingListAdapter() { 64 reset(); 65 } 66 67 /** 68 * Finds all groups of adjacent items in the cursor and calls {@link #addGroup} for each of them. 69 */ 70 protected abstract void addGroups(Cursor cursor); 71 72 protected abstract void onContentChanged(); 73 74 public void changeCursor(Cursor cursor) { 75 if (cursor == mCursor) { 76 return; 77 } 78 79 if (mCursor != null) { 80 mCursor.unregisterContentObserver(mChangeObserver); 81 mCursor.unregisterDataSetObserver(mDataSetObserver); 82 mCursor.close(); 83 } 84 85 // Reset whenever the cursor is changed. 86 reset(); 87 mCursor = cursor; 88 89 if (cursor != null) { 90 addGroups(mCursor); 91 92 // Calculate the item count by subtracting group child counts from the cursor count. 93 mItemCount = mGroupMetadata.size(); 94 95 cursor.registerContentObserver(mChangeObserver); 96 cursor.registerDataSetObserver(mDataSetObserver); 97 notifyDataSetChanged(); 98 } 99 } 100 101 /** 102 * Records information about grouping in the list. Should be called by the overridden {@link 103 * #addGroups} method. 104 */ 105 public void addGroup(int cursorPosition, int groupSize) { 106 int lastIndex = mGroupMetadata.size() - 1; 107 if (lastIndex < 0 || cursorPosition <= mGroupMetadata.keyAt(lastIndex)) { 108 mGroupMetadata.put(cursorPosition, groupSize); 109 } else { 110 // Optimization to avoid binary search if adding groups in ascending cursor position. 111 mGroupMetadata.append(cursorPosition, groupSize); 112 } 113 } 114 115 @Override 116 public int getItemCount() { 117 return mItemCount; 118 } 119 120 /** 121 * Given the position of a list item, returns the size of the group of items corresponding to that 122 * position. 123 */ 124 public int getGroupSize(int listPosition) { 125 if (listPosition < 0 || listPosition >= mGroupMetadata.size()) { 126 return 0; 127 } 128 129 return mGroupMetadata.valueAt(listPosition); 130 } 131 132 /** 133 * Given the position of a list item, returns the the first item in the group of items 134 * corresponding to that position. 135 */ 136 public Object getItem(int listPosition) { 137 if (mCursor == null || listPosition < 0 || listPosition >= mGroupMetadata.size()) { 138 return null; 139 } 140 141 int cursorPosition = mGroupMetadata.keyAt(listPosition); 142 if (mCursor.moveToPosition(cursorPosition)) { 143 return mCursor; 144 } else { 145 return null; 146 } 147 } 148 149 private void reset() { 150 mItemCount = 0; 151 mGroupMetadata = new SparseIntArray(); 152 } 153} 154