| CSipSimple 原有的分组功能只能针对连续相同被叫号码,如果中间有间隔,相同的号码就不会被分成一组。这个实现很弱,也失去了分组的意义。下面针对这块功能的设计实现做下简单记录。 |
|
|
| #### 1. 自己封装一个CursorLoader |
|
|
| 这里取名为CalllogCursorLoader,在CallLogListFragment -> OnCreateLoader中: |
|
|
| ~~~.java |
| // Loader |
| public Loader<Cursor> onCreateLoader(int id, Bundle args) { |
| return new CalllogCursorLoader(getActivity()); |
| } |
| ~~~ |
|
|
| #### 2. CalllogCursorLoader.java 代码: |
|
|
| ~~~.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 代码: |
|
|
| ~~~.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(; |
| 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 |
|
|
| ~~~.diff |
| +++ 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; |
| ~~~ |