blob: 171bf24151a3321f0e3d8dd0175366da51ce9b94 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source 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.car.dialer;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.provider.CallLog;
import android.support.annotation.NonNull;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.text.format.DateUtils;
import com.android.car.apps.common.CircleBitmapDrawable;
import com.android.car.apps.common.LetterTileDrawable;
import com.android.car.dialer.telecom.PhoneLoader;
import com.android.car.dialer.telecom.TelecomUtils;
import java.util.ArrayList;
import java.util.List;
class CallLogListingTask extends AsyncTask<Void, Void, Void> {
static class CallLogItem {
final String mTitle;
final String mText;
final String mNumber;
final Bitmap mIcon;
public CallLogItem(String title, String text, String number, Bitmap icon) {
mTitle = title;
mText = text;
mNumber = number;
mIcon = icon;
}
}
interface LoadCompleteListener {
void onLoadComplete(List<CallLogItem> items);
}
// Like a constant but needs a context so not static.
private final String VOICEMAIL_NUMBER;
private Context mContext;
private Cursor mCursor;
private List<CallLogItem> mItems;
private LoadCompleteListener mListener;
CallLogListingTask(Context context, Cursor cursor,
@NonNull LoadCompleteListener listener) {
mContext = context;
mCursor = cursor;
mItems = new ArrayList<>(mCursor.getCount());
mListener = listener;
VOICEMAIL_NUMBER = TelecomUtils.getVoicemailNumber(mContext);
}
private String maybeAppendCount(StringBuilder sb, int count) {
if (count > 1) {
sb.append(" (").append(count).append(")");
}
return sb.toString();
}
private String getContactName(String cachedName, String number,
int count, boolean isVoicemail) {
if (cachedName != null) {
return maybeAppendCount(new StringBuilder(cachedName), count);
}
StringBuilder sb = new StringBuilder();
if (isVoicemail) {
sb.append(mContext.getString(R.string.voicemail));
} else {
String displayName = TelecomUtils.getDisplayName(mContext, number);
if (TextUtils.isEmpty(displayName)) {
displayName = mContext.getString(R.string.unknown);
}
sb.append(displayName);
}
return maybeAppendCount(sb, count);
}
private Bitmap getContactImage(Context context, ContentResolver contentResolver,
String name, String number) {
Resources r = context.getResources();
int size = r.getDimensionPixelSize(R.dimen.dialer_menu_icon_container_width);
Bitmap bitmap = TelecomUtils.getContactPhotoFromNumber(contentResolver, number);
if (bitmap != null) {
return new CircleBitmapDrawable(r, bitmap).toBitmap(size);
}
LetterTileDrawable letterTileDrawable = new LetterTileDrawable(r);
letterTileDrawable.setContactDetails(name, number);
letterTileDrawable.setIsCircular(true);
return letterTileDrawable.toBitmap(size);
}
private static CharSequence getRelativeTime(long millis) {
boolean validTimestamp = millis > 0;
return validTimestamp ? DateUtils.getRelativeTimeSpanString(
millis, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE) : null;
}
@Override
protected Void doInBackground(Void... voids) {
ContentResolver resolver = mContext.getContentResolver();
try {
if (mCursor != null) {
int cachedNameColumn = PhoneLoader.getNameColumnIndex(mCursor);
int numberColumn = PhoneLoader.getNumberColumnIndex(mCursor);
int dateColumn = mCursor.getColumnIndex(CallLog.Calls.DATE);
while (mCursor.moveToNext()) {
int count = 1;
String number = mCursor.getString(numberColumn);
// We want to group calls to the same number into one so seek forward as many
// entries as possible as long as the number is the same.
int position = mCursor.getPosition();
while (mCursor.moveToNext()) {
String nextNumber = mCursor.getString(numberColumn);
if (equalNumbers(number, nextNumber)) {
count++;
} else {
break;
}
}
mCursor.moveToPosition(position);
boolean isVoicemail = number.equals(VOICEMAIL_NUMBER);
String name = getContactName(mCursor.getString(cachedNameColumn),
number, count, isVoicemail);
// Not sure why this is the only column checked here but I'm assuming this was
// to work around some bug on some device.
long millis = dateColumn == -1 ? 0 : mCursor.getLong(dateColumn);
StringBuffer secondaryText = new StringBuffer();
CharSequence relativeDate = getRelativeTime(millis);
// Append the type (work, mobile etc.) if it isnt voicemail.
if (!isVoicemail) {
CharSequence type = TelecomUtils.getTypeFromNumber(mContext, number);
secondaryText.append(type);
if (!TextUtils.isEmpty(type) && !TextUtils.isEmpty(relativeDate)) {
secondaryText.append(", ");
}
}
// Add in the timestamp.
if (relativeDate != null) {
secondaryText.append(relativeDate);
}
Bitmap contactImage = getContactImage(mContext, resolver, name, number);
CallLogItem item =
new CallLogItem(name, secondaryText.toString(), number, contactImage);
mItems.add(item);
// Since we deduplicated count rows, we can move all the way to that row so the
// next iteration takes us to the row following the last duplicate row.
if (count > 1) {
mCursor.moveToPosition(position + count - 1);
}
}
}
} finally {
if (mCursor != null) {
mCursor.close();
}
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
mListener.onLoadComplete(mItems);
}
/**
* Determines if the specified number is actually a URI
* (i.e. a SIP address) rather than a regular PSTN phone number,
* based on whether or not the number contains an "@" character.
*
* @return true if number contains @
*
* from android.telephony.PhoneNumberUtils
*/
public static boolean isUriNumber(String number) {
// Note we allow either "@" or "%40" to indicate a URI, in case
// the passed-in string is URI-escaped. (Neither "@" nor "%40"
// will ever be found in a legal PSTN number.)
return number != null && (number.contains("@") || number.contains("%40"));
}
private static boolean equalNumbers(String number1, String number2) {
if (isUriNumber(number1) || isUriNumber(number2)) {
return compareSipAddresses(number1, number2);
} else {
return PhoneNumberUtils.compare(number1, number2);
}
}
private static boolean compareSipAddresses(String number1, String number2) {
if (number1 == null || number2 == null) {
return number1 == null && number2 == null;
}
String[] address1 = splitSipAddress(number1);
String[] address2 = splitSipAddress(number2);
return address1[0].equals(address2[0]) && address1[1].equals(address2[1]);
}
/**
* Splits a sip address on either side of the @ sign and returns both halves.
* If there is no @ sign, user info will be number and rest will be empty string
* @param number the sip number to split
* @return a string array of size 2. Element 0 is the user info (left side of @ sign) and
* element 1 is the rest (right side of @ sign).
*/
private static String[] splitSipAddress(String number) {
String[] values = new String[2];
int index = number.indexOf('@');
if (index == -1) {
values[0] = number;
values[1] = "";
} else {
values[0] = number.substring(0, index);
values[1] = number.substring(index);
}
return values;
}
}