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