重新设计实现CSipSimple呼叫记录分组功能

CSipSimple 原有的分组功能只能针对连续相同被叫号码,如果中间有间隔,相同的号码就不会被分成一组。这个实现很弱,也失去了分组的意义。下面针对这块功能的设计实现做下简单记录。

1. 自己封装一个CursorLoader

这里取名为CalllogCursorLoader,在CallLogListFragment -> OnCreateLoader中:

    // Loader
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return new CalllogCursorLoader(getActivity());
    }

2. CalllogCursorLoader.java 代码:

package org.phoneos.db;

import org.phoneos.api.SipManager;

import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.CallLog;
import android.support.v4.content.AsyncTaskLoader;

public class CalllogCursorLoader extends AsyncTaskLoader<Cursor> {
    final ForceLoadContentObserver mObserver;
    private FastCursor fastCursor = null;
    private Cursor mObserverCursor = null;

    /**
     * Creates an empty unspecified CursorLoader. You must follow this with
     * calls to {@link #setUri(Uri)}, {@link #setSelection(String)}, etc to
     * specify the query to perform.
     */
    public CalllogCursorLoader(Context context) {
        super(context);
        mObserver = new ForceLoadContentObserver();
    }

    /* Runs on a worker thread */
    @Override
    public Cursor loadInBackground() {

        String[] fields = new String[] { CallLog.Calls._ID,
                CallLog.Calls.CACHED_NAME, CallLog.Calls.CACHED_NUMBER_LABEL,
                CallLog.Calls.CACHED_NUMBER_TYPE, CallLog.Calls.DURATION,
                CallLog.Calls.DATE, CallLog.Calls.NEW, CallLog.Calls.NUMBER,
                CallLog.Calls.TYPE, SipManager.CALLLOG_PROFILE_ID_FIELD };

        try {
            if (mObserverCursor != null) {
                mObserverCursor.close();
                mObserverCursor = null;
            }

            // get last inserted, a trick for observer data
            mObserverCursor = getContext().getContentResolver().query(
                    SipManager.CALLLOG_URI, fields, null, null,
                    "date desc limit 1");

            if (mObserverCursor != null) {
                mObserverCursor.registerContentObserver(mObserver);
            }

//          if (fastCursor == null) {
            Cursor cursor = getContext().getContentResolver().query(
                    SipManager.CALLLOG_URI, fields, null, null, "date asc");

            fastCursor = new FastCursor(cursor);

            cursor.close();
            cursor = null;

//          } else {
//              fastCursor.addCursor(mObserverCursor);
//          }

//          int min = fastCursor.getCount();
//          if (min > 100)
//              min = 100;
//          for (int i = 0; i < min; i++) {
//              fastCursor.moveToPosition(i);
//              Log.d("LOADER", i + ", " + fastCursor.getString(fastCursor.getColumnIndex(CallLog.Calls.NUMBER)));
//          }

            return fastCursor;
        } finally {
        }
    }

    /* Runs on the UI thread */
    @Override
    public void deliverResult(Cursor cursor) {
        if (isReset()) {
            if (fastCursor != null) {
                fastCursor.close();
            }
        }

        Cursor oldCursor = fastCursor;
        fastCursor = (FastCursor)cursor;

        if (isStarted()) {
            super.deliverResult(cursor);
        }

        if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
            oldCursor.close();
        }
    }

    /**
     * Starts an asynchronous load of the contacts list data. When the result is
     * ready the callbacks will be called on the UI thread. If a previous load
     * has been completed and is still valid the result may be passed to the
     * callbacks immediately.
     * 
     * Must be called from the UI thread
     */
    @Override
    protected void onStartLoading() {
        if (fastCursor != null) {
            deliverResult(fastCursor);
        }

        if (takeContentChanged() || fastCursor == null) {
            forceLoad();
        }
    }

    /**
     * Must be called from the UI thread
     */
    @Override
    protected void onStopLoading() {
        // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    @Override
    public void onCanceled(Cursor data) {
        if (fastCursor != null && !fastCursor.isClosed()) {
            fastCursor.close();
        }
    }

    @Override
    protected void onReset() {
        super.onReset();
        // Ensure the loader is stopped
        onStopLoading();

        if (fastCursor != null && !fastCursor.isClosed()) {
            fastCursor.close();
        }

        fastCursor = null;
    }

}

3. FastCursor.java 代码:

package org.phoneos.db;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

import android.database.AbstractCursor;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.provider.CallLog;
import android.util.Log;

// Custom a cursor, better group call logs, better performace
public class FastCursor extends AbstractCursor {
    private HashMap<String, ArrayList<Integer>> groupHashMap = new HashMap<String, ArrayList<Integer>>();
    private ArrayList<ArrayList<Integer>> groupList;
    private MatrixCursor mCursor;

    public FastCursor(Cursor cursor) {
        mCursor = new MatrixCursor(cursor.getColumnNames());

        int capacity = cursor.getCount() >> 3;
        if (capacity < 10)
            capacity = 10;
        groupList = new ArrayList<ArrayList<Integer>>(capacity);

        addCursor(cursor);
    }

    // Cursor order by date asc
    public void addCursor(Cursor cursor) {

        for (int index = 0; index < cursor.getCount(); index++) {
            cursor.moveToPosition(index);

            Object[] columnValues = new Object[cursor.getColumnCount()];
            columnValues[0] = cursor.getInt(0);
            columnValues[1] = cursor.getString(1);
            columnValues[2] = cursor.getString(2);
            columnValues[3] = cursor.getInt(3);
            columnValues[4] = cursor.getInt(4);
            columnValues[5] = cursor.getLong(5);
            columnValues[6] = cursor.getInt(6);
            columnValues[7] = cursor.getString(7);
            columnValues[8] = cursor.getInt(8);
            columnValues[9] = cursor.getInt(9);

            mCursor.addRow(columnValues);

            String number = cursor.getString(cursor.getColumnIndex(CallLog.Calls.NUMBER));
            ArrayList<Integer> list = groupHashMap.get(number);
            if (list == null) {
                list = new ArrayList<Integer>(4);
                groupHashMap.put(number, list);
            }
            else {
                groupList.remove(list);
            }

            list.add(mCursor.getCount() - 1);
            groupList.add(list);
        }

        // MUST reset mPos, workaroud for AbstractCursor.moveToPosition issue
        mPos = -1;

        Log.d("LOADER", groupHashMap.toString());
        Log.d("LOADER", groupList.toString());
    }

    @Override
    public boolean onMove(int oldPosition, int newPosition) {
        int cursorStartPos = 0;
        int length = groupList.size();
        ArrayList<Integer> list = null;
        for (int i = length - 1; i >= 0; i--) {
            if (newPosition < (cursorStartPos + groupList.get(i).size())) {
                list = groupList.get(i);
                break;
            }

            cursorStartPos += groupList.get(i).size();
        }

        /* Move it to the right position */
        if (list != null) {
            int index = list.size() - (newPosition - cursorStartPos) - 1;
            Boolean ret = mCursor.moveToPosition(list.get(index));
            return ret;
        }
        return false;
    }

    // AbstractCursor implementation.

    @Override
    public void close() {
        super.close();

        mCursor.close();
        groupHashMap.clear();
        groupList.clear();

        mCursor = null;
        groupHashMap = null;
        groupList = null;
    }

    @Override
    public int getCount() {
        return mCursor.getCount();
    }

    @Override
    public String[] getColumnNames() {
        return mCursor.getColumnNames();
    }

    @Override
    public String getString(int column) {
        return mCursor.getString(column);
    }

    @Override
    public short getShort(int column) {
        return mCursor.getShort(column);
    }

    @Override
    public int getInt(int column) {
        return mCursor.getInt(column);
    }

    @Override
    public long getLong(int column) {
        return mCursor.getLong(column);
    }

    @Override
    public float getFloat(int column) {
        return mCursor.getFloat(column);
    }

    @Override
    public double getDouble(int column) {
        return mCursor.getDouble(column);
    }

    @Override
    public byte[] getBlob(int column) {
        return mCursor.getBlob(column);
    }

    @Override
    public int getType(int column) {
        return mCursor.getType(column);
    }

    @Override
    public boolean isNull(int column) {
        return mCursor.isNull(column);
    }

}

4. 修改CallLogGroupBuilder.java

+++ b/src/org/phoneos/ui/calllog/CallLogGroupBuilder.java
@@ -81,16 +81,17 @@ public class CallLogGroupBuilder {
             final boolean sameNumber = equalNumbers(firstNumber, currentNumber);
             final boolean shouldGroup;

-            if (!sameNumber) {
-                // Should only group with calls from the same number.
-                shouldGroup = false;
-            } else if ( firstCallType == Calls.MISSED_TYPE) {
-                // Voicemail and missed calls should only be grouped with subsequent missed calls.
-                shouldGroup = callType == Calls.MISSED_TYPE;
-            } else {
-                // Incoming and outgoing calls group together.
-                shouldGroup = callType == Calls.INCOMING_TYPE || callType == Calls.OUTGOING_TYPE;
-            }
+            shouldGroup = sameNumber;

留言