Home | History | Annotate | Download | only in calllog
      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