diff options
Diffstat (limited to 'src/com/android/dialer/callstats/CallStatsQueryHandler.java')
-rw-r--r-- | src/com/android/dialer/callstats/CallStatsQueryHandler.java | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/src/com/android/dialer/callstats/CallStatsQueryHandler.java b/src/com/android/dialer/callstats/CallStatsQueryHandler.java new file mode 100644 index 000000000..f3590554e --- /dev/null +++ b/src/com/android/dialer/callstats/CallStatsQueryHandler.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2013 Android Open Kang Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dialer.callstats; + +import android.content.AsyncQueryHandler; +import android.content.ContentResolver; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabaseCorruptException; +import android.database.sqlite.SQLiteDiskIOException; +import android.database.sqlite.SQLiteFullException; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.provider.CallLog.Calls; +import android.util.Log; + +import com.android.contacts.common.CallUtil; +import com.android.contacts.common.util.UriUtils; +import com.android.dialer.calllog.ContactInfo; +import com.google.common.collect.Lists; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Class to handle call-log queries, optionally with a date-range filter + */ +public class CallStatsQueryHandler extends AsyncQueryHandler { + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + + private static final int EVENT_PROCESS_DATA = 10; + + private static final int QUERY_CALLS_TOKEN = 100; + + public static final int CALL_TYPE_ALL = 0; + + private static final String TAG = "CallStatsQueryHandler"; + + private final WeakReference<Listener> mListener; + private Handler mWorkerThreadHandler; + + /** + * Simple handler that wraps background calls to catch + * {@link SQLiteException}, such as when the disk is full. + */ + protected class CatchingWorkerHandler extends AsyncQueryHandler.WorkerHandler { + public CatchingWorkerHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + if (msg.arg1 == EVENT_PROCESS_DATA) { + Cursor cursor = (Cursor) msg.obj; + Message reply = CallStatsQueryHandler.this.obtainMessage(msg.what); + reply.obj = processData(cursor); + reply.arg1 = msg.arg1; + reply.sendToTarget(); + return; + } + + try { + // Perform same query while catching any exceptions + super.handleMessage(msg); + } catch (SQLiteDiskIOException e) { + Log.w(TAG, "Exception on background worker thread", e); + } catch (SQLiteFullException e) { + Log.w(TAG, "Exception on background worker thread", e); + } catch (SQLiteDatabaseCorruptException e) { + Log.w(TAG, "Exception on background worker thread", e); + } + } + } + + @Override + protected Handler createHandler(Looper looper) { + // Provide our special handler that catches exceptions + mWorkerThreadHandler = new CatchingWorkerHandler(looper); + return mWorkerThreadHandler; + } + + public CallStatsQueryHandler(ContentResolver contentResolver, Listener listener) { + super(contentResolver); + mListener = new WeakReference<Listener>(listener); + } + + public void fetchCalls(long from, long to) { + cancelOperation(QUERY_CALLS_TOKEN); + + StringBuilder selection = new StringBuilder(); + List<String> selectionArgs = Lists.newArrayList(); + + if (from != -1) { + selection.append(String.format("(%s > ?)", Calls.DATE)); + selectionArgs.add(String.valueOf(from)); + } + if (to != -1) { + if (selection.length() > 0) { + selection.append(" AND "); + } + selection.append(String.format("(%s < ?)", Calls.DATE)); + selectionArgs.add(String.valueOf(to)); + } + + startQuery(QUERY_CALLS_TOKEN, null, Calls.CONTENT_URI, CallStatsQuery._PROJECTION, + selection.toString(), selectionArgs.toArray(EMPTY_STRING_ARRAY), + Calls.NUMBER + " ASC"); + } + + @Override + protected synchronized void onQueryComplete(int token, Object cookie, Cursor cursor) { + if (token == QUERY_CALLS_TOKEN) { + Message msg = mWorkerThreadHandler.obtainMessage(token); + msg.arg1 = EVENT_PROCESS_DATA; + msg.obj = cursor; + + mWorkerThreadHandler.sendMessage(msg); + } + } + + @Override + public void handleMessage(Message msg) { + if (msg.arg1 == EVENT_PROCESS_DATA) { + final Map<ContactInfo, CallStatsDetails> calls = + (Map<ContactInfo, CallStatsDetails>) msg.obj; + final Listener listener = mListener.get(); + if (listener != null) { + listener.onCallsFetched(calls); + } + } else { + super.handleMessage(msg); + } + } + + private Map<ContactInfo, CallStatsDetails> processData(Cursor cursor) { + final Map<ContactInfo, CallStatsDetails> result = new HashMap<ContactInfo, CallStatsDetails>(); + final ArrayList<ContactInfo> infos = new ArrayList<ContactInfo>(); + final ArrayList<CallStatsDetails> calls = new ArrayList<CallStatsDetails>(); + CallStatsDetails pending = null; + + cursor.moveToFirst(); + + while (!cursor.isAfterLast()) { + final String number = cursor.getString(CallStatsQuery.NUMBER); + final long duration = cursor.getLong(CallStatsQuery.DURATION); + final int callType = cursor.getInt(CallStatsQuery.CALL_TYPE); + + if (pending == null || !CallUtil.phoneNumbersEqual(pending.number.toString(), number)) { + final long date = cursor.getLong(CallStatsQuery.DATE); + final int numberPresentation = cursor.getInt(CallStatsQuery.NUMBER_PRESENTATION); + final String countryIso = cursor.getString(CallStatsQuery.COUNTRY_ISO); + final String geocode = cursor.getString(CallStatsQuery.GEOCODED_LOCATION); + final ContactInfo info = getContactInfoFromCallStats(cursor); + + pending = new CallStatsDetails(number, numberPresentation, + info, countryIso, geocode, date); + infos.add(info); + calls.add(pending); + } + + pending.addTimeOrMissed(callType, duration); + cursor.moveToNext(); + } + + cursor.close(); + mergeItemsByNumber(calls, infos); + + for (int i = 0; i < calls.size(); i++) { + result.put(infos.get(i), calls.get(i)); + } + + return result; + } + + private void mergeItemsByNumber(List<CallStatsDetails> calls, List<ContactInfo> infos) { + // temporarily store items marked for removal + final ArrayList<CallStatsDetails> callsToRemove = new ArrayList<CallStatsDetails>(); + final ArrayList<ContactInfo> infosToRemove = new ArrayList<ContactInfo>(); + + for (int i = 0; i < calls.size(); i++) { + final CallStatsDetails outerItem = calls.get(i); + final String currentFormattedNumber = outerItem.number.toString(); + + for (int j = calls.size() - 1; j > i; j--) { + final CallStatsDetails innerItem = calls.get(j); + final String innerNumber = innerItem.number.toString(); + + if (CallUtil.phoneNumbersEqual(currentFormattedNumber, innerNumber)) { + outerItem.mergeWith(innerItem); + //make sure we're not counting twice in case we're dealing with + //multiple different formats + innerItem.reset(); + callsToRemove.add(innerItem); + infosToRemove.add(infos.get(j)); + } + } + } + + for (CallStatsDetails call : callsToRemove) { + calls.remove(call); + } + for (ContactInfo info : infosToRemove) { + infos.remove(info); + } + } + + private ContactInfo getContactInfoFromCallStats(Cursor c) { + ContactInfo info = new ContactInfo(); + info.lookupUri = UriUtils.parseUriOrNull(c.getString(CallStatsQuery.CACHED_LOOKUP_URI)); + info.name = c.getString(CallStatsQuery.CACHED_NAME); + info.type = c.getInt(CallStatsQuery.CACHED_NUMBER_TYPE); + info.label = c.getString(CallStatsQuery.CACHED_NUMBER_LABEL); + + final String matchedNumber = c.getString(CallStatsQuery.CACHED_MATCHED_NUMBER); + info.number = matchedNumber == null ? c.getString(CallStatsQuery.NUMBER) : matchedNumber; + info.normalizedNumber = c.getString(CallStatsQuery.CACHED_NORMALIZED_NUMBER); + info.formattedNumber = c.getString(CallStatsQuery.CACHED_FORMATTED_NUMBER); + + info.photoId = c.getLong(CallStatsQuery.CACHED_PHOTO_ID); + info.photoUri = null; // We do not cache the photo URI. + + return info; + } + + public interface Listener { + void onCallsFetched(Map<ContactInfo, CallStatsDetails> calls); + } +} |