• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GroupingListAdapter(Context context)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      */
addGroups(Cursor cursor)74     protected abstract void addGroups(Cursor cursor);
75 
addVoicemailGroups(Cursor cursor)76     protected abstract void addVoicemailGroups(Cursor cursor);
77 
onContentChanged()78     protected abstract void onContentChanged();
79 
changeCursor(Cursor cursor)80     public void changeCursor(Cursor cursor) {
81         changeCursor(cursor, false);
82     }
83 
changeCursorVoicemail(Cursor cursor)84     public void changeCursorVoicemail(Cursor cursor) {
85         changeCursor(cursor, true);
86     }
87 
changeCursor(Cursor cursor, boolean voicemail)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      */
addGroup(int cursorPosition, int groupSize)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
getItemCount()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      */
getGroupSize(int listPosition)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      */
getItem(int listPosition)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 
reset()167     private void reset() {
168         mItemCount = 0;
169         mGroupMetadata = new SparseIntArray();
170     }
171 }
172