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