1 /* 2 * Copyright (C) 2019 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.car.telephony.common; 18 19 import android.content.Context; 20 import android.database.Cursor; 21 import android.provider.CallLog; 22 import android.text.TextUtils; 23 import android.text.format.DateUtils; 24 25 import androidx.annotation.IntDef; 26 import androidx.annotation.NonNull; 27 28 import com.google.common.base.Preconditions; 29 30 import java.util.ArrayList; 31 import java.util.Collections; 32 import java.util.List; 33 import java.util.Objects; 34 import java.util.concurrent.TimeUnit; 35 36 /** 37 * Entity class for call logs of a phone number. This call log may contains multiple call 38 * records. 39 */ 40 public class PhoneCallLog { 41 private static final String TAG = "CD.PhoneCallLog"; 42 43 @IntDef({TimeRange.TODAY, TimeRange.YESTERDAY, TimeRange.OLDER}) 44 public @interface TimeRange { 45 int TODAY = 0; 46 int YESTERDAY = 1; 47 int OLDER = 2; 48 } 49 50 /** Call log record. */ 51 public static class Record implements Comparable<Record> { 52 private final long mCallEndTimestamp; 53 private final int mCallType; 54 Record(long callEndTimestamp, int callType)55 public Record(long callEndTimestamp, int callType) { 56 mCallEndTimestamp = callEndTimestamp; 57 mCallType = callType; 58 } 59 60 /** Returns the timestamp on when the call occured, in milliseconds since the epoch */ getCallEndTimestamp()61 public long getCallEndTimestamp() { 62 return mCallEndTimestamp; 63 } 64 65 /** 66 * Returns the type of this record. For example, missed call, outbound call. Allowed values 67 * are defined in {@link CallLog.Calls#TYPE}. 68 * 69 * @see CallLog.Calls#TYPE 70 */ getCallType()71 public int getCallType() { 72 return mCallType; 73 } 74 75 /** Phone call records are sort in reverse chronological order. */ 76 @Override compareTo(Record otherRecord)77 public int compareTo(Record otherRecord) { 78 return (int) (otherRecord.mCallEndTimestamp - mCallEndTimestamp); 79 } 80 } 81 82 private long mId; 83 private String mPhoneNumberString; 84 private I18nPhoneNumberWrapper mI18nPhoneNumberWrapper; 85 private String mAccountName; 86 private List<Record> mCallRecords = new ArrayList<>(); 87 private int mTimeRange; 88 89 /** 90 * Creates a {@link PhoneCallLog} from a {@link Cursor}. 91 */ fromCursor(Context context, Cursor cursor)92 public static PhoneCallLog fromCursor(Context context, Cursor cursor) { 93 int idColumn = cursor.getColumnIndex(CallLog.Calls._ID); 94 int numberColumn = cursor.getColumnIndex(CallLog.Calls.NUMBER); 95 int dateColumn = cursor.getColumnIndex(CallLog.Calls.DATE); 96 int callTypeColumn = cursor.getColumnIndex(CallLog.Calls.TYPE); 97 int accountNameColumn = cursor.getColumnIndex(CallLog.Calls.PHONE_ACCOUNT_ID); 98 99 PhoneCallLog phoneCallLog = new PhoneCallLog(); 100 phoneCallLog.mId = cursor.getLong(idColumn); 101 phoneCallLog.mPhoneNumberString = cursor.getString(numberColumn); 102 phoneCallLog.mI18nPhoneNumberWrapper = I18nPhoneNumberWrapper.Factory.INSTANCE.get(context, 103 phoneCallLog.mPhoneNumberString); 104 Record record = new Record(cursor.getLong(dateColumn), cursor.getInt(callTypeColumn)); 105 phoneCallLog.mCallRecords.add(record); 106 phoneCallLog.mTimeRange = getTimeRange(record.getCallEndTimestamp()); 107 phoneCallLog.mAccountName = cursor.getString(accountNameColumn); 108 return phoneCallLog; 109 } 110 111 /** Returns the phone number of this log. */ getPhoneNumberString()112 public String getPhoneNumberString() { 113 return mPhoneNumberString; 114 } 115 116 /** 117 * Returns the account name that this call log belongs to. For call logs from Bluetooth device, 118 * account name is the same as Bluetooth address. 119 */ getAccountName()120 public String getAccountName() { 121 return mAccountName; 122 } 123 124 /** Returns the id of this log. */ getPhoneLogId()125 public long getPhoneLogId() { 126 return mId; 127 } 128 129 /** Returns the last call end timestamp of this number. */ getLastCallEndTimestamp()130 public long getLastCallEndTimestamp() { 131 Preconditions.checkState(!mCallRecords.isEmpty(), "Unexpected empty call records"); 132 return mCallRecords.get(0).getCallEndTimestamp(); 133 } 134 135 /** 136 * Returns a copy of records from the phone number. Logs are sorted from most recent to least 137 * recent call end time. 138 */ getAllCallRecords()139 public List<Record> getAllCallRecords() { 140 return new ArrayList<>(mCallRecords); 141 } 142 143 /** Returns the time range when the phone call was made. */ 144 @TimeRange getTimeRange()145 public int getTimeRange() { 146 return mTimeRange; 147 } 148 149 /** 150 * Merges all call records with this call log's call records if they are representing the same 151 * phone number. 152 * 153 * @param checkTimeRange if true, only merge the call records if they are in the same time range 154 */ merge(@onNull PhoneCallLog phoneCallLog, boolean checkTimeRange)155 public boolean merge(@NonNull PhoneCallLog phoneCallLog, boolean checkTimeRange) { 156 if (!equals(phoneCallLog)) { 157 return false; 158 } 159 if (checkTimeRange && mTimeRange != phoneCallLog.getTimeRange()) { 160 return false; 161 } 162 mCallRecords.addAll(phoneCallLog.mCallRecords); 163 Collections.sort(mCallRecords); 164 return true; 165 } 166 167 @Override equals(Object object)168 public boolean equals(Object object) { 169 if (object instanceof PhoneCallLog) { 170 // We compare the ids when the phone number string is empty. 171 if (TextUtils.isEmpty(mPhoneNumberString)) { 172 return mId == ((PhoneCallLog) object).mId; 173 } else { 174 return mI18nPhoneNumberWrapper.equals( 175 ((PhoneCallLog) object).mI18nPhoneNumberWrapper); 176 } 177 } 178 return false; 179 } 180 181 @Override hashCode()182 public int hashCode() { 183 if (TextUtils.isEmpty(mPhoneNumberString)) { 184 return Long.hashCode(mId); 185 } else { 186 return Objects.hashCode(mI18nPhoneNumberWrapper); 187 } 188 } 189 190 @Override toString()191 public String toString() { 192 StringBuilder sb = new StringBuilder(); 193 sb.append("PhoneNumber: "); 194 sb.append(TelecomUtils.piiLog(mPhoneNumberString)); 195 sb.append(" CallLog: "); 196 sb.append(mCallRecords.size()); 197 sb.append(" Account: "); 198 sb.append(mAccountName); 199 return sb.toString(); 200 } 201 202 @TimeRange getTimeRange(long callLogTime)203 private static int getTimeRange(long callLogTime) { 204 if (DateUtils.isToday(callLogTime)) { 205 return TimeRange.TODAY; 206 } 207 208 if (DateUtils.isToday(callLogTime + TimeUnit.DAYS.toMillis(1))) { 209 return TimeRange.YESTERDAY; 210 } 211 212 return TimeRange.OLDER; 213 } 214 } 215