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 17 package com.android.dialer.calllog; 18 19 import android.content.Context; 20 import android.database.ContentObserver; 21 import android.database.Cursor; 22 import android.database.DataSetObserver; 23 import android.os.Handler; 24 import android.support.v7.widget.RecyclerView; 25 import android.util.SparseIntArray; 26 27 /** 28 * Maintains a list that groups items into groups of consecutive elements which are disjoint, 29 * that is, an item can only belong to one group. This is leveraged for grouping calls in the 30 * call log received from or made to the same phone number. 31 * 32 * There are two integers stored as metadata for every list item in the adapter. 33 */ 34 abstract class GroupingListAdapter extends RecyclerView.Adapter { 35 36 private Context mContext; 37 private Cursor mCursor; 38 39 /** 40 * SparseIntArray, which maps the cursor position of the first element of a group to the size 41 * of the group. The index of a key in this map corresponds to the list position of that group. 42 */ 43 private SparseIntArray mGroupMetadata; 44 private int mItemCount; 45 46 protected ContentObserver mChangeObserver = new ContentObserver(new Handler()) { 47 @Override 48 public boolean deliverSelfNotifications() { 49 return true; 50 } 51 52 @Override 53 public void onChange(boolean selfChange) { 54 onContentChanged(); 55 } 56 }; 57 58 protected DataSetObserver mDataSetObserver = new DataSetObserver() { 59 @Override 60 public void onChanged() { 61 notifyDataSetChanged(); 62 } 63 }; 64 65 public GroupingListAdapter(Context context) { 66 mContext = context; 67 reset(); 68 } 69 70 /** 71 * Finds all groups of adjacent items in the cursor and calls {@link #addGroup} for 72 * each of them. 73 */ 74 protected abstract void addGroups(Cursor cursor); 75 76 protected abstract void addVoicemailGroups(Cursor cursor); 77 78 protected abstract void onContentChanged(); 79 80 public void changeCursor(Cursor cursor) { 81 changeCursor(cursor, false); 82 } 83 84 public void changeCursorVoicemail(Cursor cursor) { 85 changeCursor(cursor, true); 86 } 87 88 public void changeCursor(Cursor cursor, boolean voicemail) { 89 if (cursor == mCursor) { 90 return; 91 } 92 93 if (mCursor != null) { 94 mCursor.unregisterContentObserver(mChangeObserver); 95 mCursor.unregisterDataSetObserver(mDataSetObserver); 96 mCursor.close(); 97 } 98 99 // Reset whenever the cursor is changed. 100 reset(); 101 mCursor = cursor; 102 103 if (cursor != null) { 104 if (voicemail) { 105 addVoicemailGroups(mCursor); 106 } else { 107 addGroups(mCursor); 108 } 109 110 // Calculate the item count by subtracting group child counts from the cursor count. 111 mItemCount = mGroupMetadata.size(); 112 113 cursor.registerContentObserver(mChangeObserver); 114 cursor.registerDataSetObserver(mDataSetObserver); 115 notifyDataSetChanged(); 116 } 117 } 118 119 /** 120 * Records information about grouping in the list. 121 * Should be called by the overridden {@link #addGroups} method. 122 */ 123 public void addGroup(int cursorPosition, int groupSize) { 124 int lastIndex = mGroupMetadata.size() - 1; 125 if (lastIndex < 0 || cursorPosition <= mGroupMetadata.keyAt(lastIndex)) { 126 mGroupMetadata.put(cursorPosition, groupSize); 127 } else { 128 // Optimization to avoid binary search if adding groups in ascending cursor position. 129 mGroupMetadata.append(cursorPosition, groupSize); 130 } 131 } 132 133 @Override 134 public int getItemCount() { 135 return mItemCount; 136 } 137 138 /** 139 * Given the position of a list item, returns the size of the group of items corresponding to 140 * that position. 141 */ 142 public int getGroupSize(int listPosition) { 143 if (listPosition < 0 || listPosition >= mGroupMetadata.size()) { 144 return 0; 145 } 146 147 return mGroupMetadata.valueAt(listPosition); 148 } 149 150 /** 151 * Given the position of a list item, returns the the first item in the group of items 152 * corresponding to that position. 153 */ 154 public Object getItem(int listPosition) { 155 if (mCursor == null || listPosition < 0 || listPosition >= mGroupMetadata.size()) { 156 return null; 157 } 158 159 int cursorPosition = mGroupMetadata.keyAt(listPosition); 160 if (mCursor.moveToPosition(cursorPosition)) { 161 return mCursor; 162 } else { 163 return null; 164 } 165 } 166 167 private void reset() { 168 mItemCount = 0; 169 mGroupMetadata = new SparseIntArray(); 170 } 171 } 172