18369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian/* 28369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * Copyright (C) 2017 The Android Open Source Project 38369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * 48369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * Licensed under the Apache License, Version 2.0 (the "License"); 58369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * you may not use this file except in compliance with the License. 68369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * You may obtain a copy of the License at 78369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * 88369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * http://www.apache.org/licenses/LICENSE-2.0 98369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * 108369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * Unless required by applicable law or agreed to in writing, software 118369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * distributed under the License is distributed on an "AS IS" BASIS, 128369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 138369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * See the License for the specific language governing permissions and 148369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * limitations under the License 158369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian */ 168369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian 178369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanianpackage com.android.dialer.calllog.datasources.systemcalllog; 188369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian 192f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport android.Manifest.permission; 202f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport android.annotation.TargetApi; 212f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport android.content.ContentValues; 228369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanianimport android.content.Context; 238369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanianimport android.database.ContentObserver; 242f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport android.database.Cursor; 258369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanianimport android.net.Uri; 262f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport android.os.Build; 278369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanianimport android.os.Handler; 282f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport android.preference.PreferenceManager; 298369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanianimport android.provider.CallLog; 302f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport android.provider.CallLog.Calls; 318369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanianimport android.support.annotation.MainThread; 322f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport android.support.annotation.Nullable; 332f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport android.support.annotation.VisibleForTesting; 348369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanianimport android.support.annotation.WorkerThread; 352f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport android.text.TextUtils; 362f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport android.util.ArraySet; 372f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport com.android.dialer.DialerPhoneNumber; 382f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; 392f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.CoalescedAnnotatedCallLog; 408369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanianimport com.android.dialer.calllog.datasources.CallLogDataSource; 412f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport com.android.dialer.calllog.datasources.CallLogMutations; 422f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport com.android.dialer.calllog.datasources.util.RowCombiner; 438369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanianimport com.android.dialer.common.Assert; 448369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanianimport com.android.dialer.common.LogUtil; 458369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanianimport com.android.dialer.common.concurrent.ThreadUtil; 462f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport com.android.dialer.phonenumberproto.DialerPhoneNumberUtil; 47c857f90590e7d7fcffa89511982eb33afd34805fEric Erfanianimport com.android.dialer.util.PermissionsUtil; 482f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport com.google.i18n.phonenumbers.PhoneNumberUtil; 492f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport com.google.protobuf.InvalidProtocolBufferException; 502f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport java.util.Arrays; 512f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport java.util.List; 522f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport java.util.Set; 538369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanianimport javax.inject.Inject; 548369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian 558369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian/** 568369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * Responsible for defining the rows in the annotated call log and maintaining the columns in it 578369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * which are derived from the system call log. 588369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian */ 592f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian@SuppressWarnings("MissingPermission") 608369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanianpublic class SystemCallLogDataSource implements CallLogDataSource { 618369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian 622f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian @VisibleForTesting 632f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian static final String PREF_LAST_TIMESTAMP_PROCESSED = "systemCallLogLastTimestampProcessed"; 642f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 652f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian @Nullable private Long lastTimestampProcessed; 662f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 678369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian @Inject 688369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian public SystemCallLogDataSource() {} 698369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian 708369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian @MainThread 718369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian @Override 728369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian public void registerContentObservers( 738369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian Context appContext, ContentObserverCallbacks contentObserverCallbacks) { 748369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian Assert.isMainThread(); 758369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian 762f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian LogUtil.enterBlock("SystemCallLogDataSource.registerContentObservers"); 772f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 78c857f90590e7d7fcffa89511982eb33afd34805fEric Erfanian if (!PermissionsUtil.hasCallLogReadPermissions(appContext)) { 79c857f90590e7d7fcffa89511982eb33afd34805fEric Erfanian LogUtil.i("SystemCallLogDataSource.registerContentObservers", "no call log permissions"); 80c857f90590e7d7fcffa89511982eb33afd34805fEric Erfanian return; 81c857f90590e7d7fcffa89511982eb33afd34805fEric Erfanian } 82c857f90590e7d7fcffa89511982eb33afd34805fEric Erfanian 838369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian appContext 848369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian .getContentResolver() 858369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian .registerContentObserver( 868369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian CallLog.Calls.CONTENT_URI, 878369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian true, 888369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian new CallLogObserver( 898369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian ThreadUtil.getUiThreadHandler(), appContext, contentObserverCallbacks)); 908369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian } 918369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian 928369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian @WorkerThread 938369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian @Override 948369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian public boolean isDirty(Context appContext) { 958369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian Assert.isWorkerThread(); 968369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian 978369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian /* 988369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * The system call log has a last updated timestamp, but deletes are physical (the "deleted" 998369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * column is unused). This means that we can't detect deletes without scanning the entire table, 1008369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * which would be too slow. So, we just rely on content observers to trigger rebuilds when any 1018369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * change is made to the system call log. 1028369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian */ 1038369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian return false; 1048369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian } 1058369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian 1068369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian @WorkerThread 1078369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian @Override 1082f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian public void fill(Context appContext, CallLogMutations mutations) { 1098369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian Assert.isWorkerThread(); 1108369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian 1112f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian lastTimestampProcessed = null; 1122f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 1132f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian if (!PermissionsUtil.hasPermission(appContext, permission.READ_CALL_LOG)) { 1142f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian LogUtil.i("SystemCallLogDataSource.fill", "no call log permissions"); 1152f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian return; 1162f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 1172f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 1188369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian // This data source should always run first so the mutations should always be empty. 1192f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian Assert.checkArgument(mutations.isEmpty()); 1202f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 1212f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian Set<Long> annotatedCallLogIds = getAnnotatedCallLogIds(appContext); 1222f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 1232f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian LogUtil.i( 1242f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian "SystemCallLogDataSource.fill", 1252f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian "found %d existing annotated call log ids", 1262f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian annotatedCallLogIds.size()); 1272f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 1282f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian handleInsertsAndUpdates(appContext, mutations, annotatedCallLogIds); 1292f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian handleDeletes(appContext, annotatedCallLogIds, mutations); 1302f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 1312f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 1322f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian @WorkerThread 1332f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian @Override 1342f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian public void onSuccessfulFill(Context appContext) { 1352f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian // If a fill operation was a no-op, lastTimestampProcessed could still be null. 1362f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian if (lastTimestampProcessed != null) { 1372f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian PreferenceManager.getDefaultSharedPreferences(appContext) 1382f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian .edit() 1392f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian .putLong(PREF_LAST_TIMESTAMP_PROCESSED, lastTimestampProcessed) 1402f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian .apply(); 1412f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 1422f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 1432f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 1442f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian @Override 1452f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian public ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc) { 14610ae593a59aa50963e1d3159747da2d65ca79bedEric Erfanian // TODO: Complete implementation. 1472f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian ContentValues coalescedValues = 1482f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian new RowCombiner(individualRowsSortedByTimestampDesc) 1492f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian .useMostRecentLong(AnnotatedCallLog.TIMESTAMP) 1502f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian .combine(); 1512f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 1522f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian // All phone numbers in the provided group should be equivalent (but could be formatted 1532f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian // differently). Arbitrarily show the raw phone number of the most recent call. 1542f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian DialerPhoneNumber mostRecentPhoneNumber = 1552f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian getMostRecentPhoneNumber(individualRowsSortedByTimestampDesc); 1562f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian coalescedValues.put( 1572f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian CoalescedAnnotatedCallLog.FORMATTED_NUMBER, 1582f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian mostRecentPhoneNumber.getRawInput().getNumber()); 1592f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian return coalescedValues; 1602f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 1612f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 1622f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian private static DialerPhoneNumber getMostRecentPhoneNumber( 1632f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian List<ContentValues> individualRowsSortedByTimestampDesc) { 1642f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian DialerPhoneNumber dialerPhoneNumber; 1652f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian byte[] protoBytes = 1662f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian individualRowsSortedByTimestampDesc.get(0).getAsByteArray(AnnotatedCallLog.NUMBER); 1672f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian try { 1682f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian dialerPhoneNumber = DialerPhoneNumber.parseFrom(protoBytes); 1692f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } catch (InvalidProtocolBufferException e) { 1702f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian throw Assert.createAssertionFailException("couldn't parse DialerPhoneNumber", e); 1712f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 1722f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian return dialerPhoneNumber; 1732f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 1742f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 1752f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian @TargetApi(Build.VERSION_CODES.M) // Uses try-with-resources 1762f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian private void handleInsertsAndUpdates( 1772f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian Context appContext, CallLogMutations mutations, Set<Long> existingAnnotatedCallLogIds) { 1782f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian long previousTimestampProcessed = 1792f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian PreferenceManager.getDefaultSharedPreferences(appContext) 1802f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian .getLong(PREF_LAST_TIMESTAMP_PROCESSED, 0L); 1812f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 1822f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian DialerPhoneNumberUtil dialerPhoneNumberUtil = 1832f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian new DialerPhoneNumberUtil(PhoneNumberUtil.getInstance()); 1842f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 18510ae593a59aa50963e1d3159747da2d65ca79bedEric Erfanian // TODO: Really should be getting last 1000 by timestamp, not by last modified. 1862f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian try (Cursor cursor = 1872f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian appContext 1882f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian .getContentResolver() 1892f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian .query( 1902f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian Calls.CONTENT_URI, // Excludes voicemail 1912f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian new String[] { 1922f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian Calls._ID, Calls.DATE, Calls.LAST_MODIFIED, Calls.NUMBER, Calls.COUNTRY_ISO 1932f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian }, 1942f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian Calls.LAST_MODIFIED + " > ?", 1952f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian new String[] {String.valueOf(previousTimestampProcessed)}, 1962f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian Calls.LAST_MODIFIED + " DESC LIMIT 1000")) { 1978369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian 1982f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian if (cursor == null) { 1992f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian LogUtil.e("SystemCallLogDataSource.handleInsertsAndUpdates", "null cursor"); 2002f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian return; 2012f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 2022f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 2032f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian LogUtil.i( 2042f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian "SystemCallLogDataSource.handleInsertsAndUpdates", 2052f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian "found %d entries to insert/update", 2062f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian cursor.getCount()); 2072f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 2082f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian if (cursor.moveToFirst()) { 2092f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian int idColumn = cursor.getColumnIndexOrThrow(Calls._ID); 2102f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian int dateColumn = cursor.getColumnIndexOrThrow(Calls.DATE); 2112f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian int lastModifiedColumn = cursor.getColumnIndexOrThrow(Calls.LAST_MODIFIED); 2122f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian int numberColumn = cursor.getColumnIndexOrThrow(Calls.NUMBER); 2132f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian int countryIsoColumn = cursor.getColumnIndexOrThrow(Calls.COUNTRY_ISO); 2142f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 2152f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian // The cursor orders by LAST_MODIFIED DESC, so the first result is the most recent timestamp 2162f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian // processed. 2172f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian lastTimestampProcessed = cursor.getLong(lastModifiedColumn); 2182f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian do { 2192f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian long id = cursor.getLong(idColumn); 2202f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian long date = cursor.getLong(dateColumn); 2212f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian String numberAsStr = cursor.getString(numberColumn); 2222f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian String countryIso = cursor.getString(countryIsoColumn); 2232f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 2242f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian byte[] numberAsProtoBytes = 2252f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian dialerPhoneNumberUtil.parse(numberAsStr, countryIso).toByteArray(); 2262f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 2272f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian ContentValues contentValues = new ContentValues(); 2282f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian contentValues.put(AnnotatedCallLog.TIMESTAMP, date); 2292f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian contentValues.put(AnnotatedCallLog.NUMBER, numberAsProtoBytes); 2302f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 2312f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian if (existingAnnotatedCallLogIds.contains(id)) { 2322f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian mutations.update(id, contentValues); 2332f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } else { 2342f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian mutations.insert(id, contentValues); 2352f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 2362f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } while (cursor.moveToNext()); 2372f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } // else no new results, do nothing. 2382f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 2392f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 2402f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 2412f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian private static void handleDeletes( 2422f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian Context appContext, Set<Long> existingAnnotatedCallLogIds, CallLogMutations mutations) { 2432f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian Set<Long> systemCallLogIds = 2442f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian getIdsFromSystemCallLogThatMatch(appContext, existingAnnotatedCallLogIds); 2452f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian LogUtil.i( 2462f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian "SystemCallLogDataSource.handleDeletes", 2472f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian "found %d matching entries in system call log", 2482f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian systemCallLogIds.size()); 2492f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian Set<Long> idsInAnnotatedCallLogNoLongerInSystemCallLog = new ArraySet<>(); 2502f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian idsInAnnotatedCallLogNoLongerInSystemCallLog.addAll(existingAnnotatedCallLogIds); 2512f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian idsInAnnotatedCallLogNoLongerInSystemCallLog.removeAll(systemCallLogIds); 2522f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 2532f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian LogUtil.i( 2542f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian "SystemCallLogDataSource.handleDeletes", 2552f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian "found %d call log entries to remove", 2562f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian idsInAnnotatedCallLogNoLongerInSystemCallLog.size()); 2572f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 2582f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian for (long id : idsInAnnotatedCallLogNoLongerInSystemCallLog) { 2592f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian mutations.delete(id); 2602f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 2612f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 2622f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 2632f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian @TargetApi(Build.VERSION_CODES.M) // Uses try-with-resources 2642f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian private static Set<Long> getAnnotatedCallLogIds(Context appContext) { 2652f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian ArraySet<Long> ids = new ArraySet<>(); 2662f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 2672f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian try (Cursor cursor = 2682f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian appContext 2692f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian .getContentResolver() 2702f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian .query( 2712f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian AnnotatedCallLog.CONTENT_URI, 2722f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian new String[] {AnnotatedCallLog._ID}, 2732f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian null, 2742f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian null, 2752f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian null)) { 2762f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 2772f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian if (cursor == null) { 2782f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian LogUtil.e("SystemCallLogDataSource.getAnnotatedCallLogIds", "null cursor"); 2792f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian return ids; 2802f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 2812f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 2822f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian if (cursor.moveToFirst()) { 2832f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian int idColumn = cursor.getColumnIndexOrThrow(AnnotatedCallLog._ID); 2842f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian do { 2852f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian ids.add(cursor.getLong(idColumn)); 2862f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } while (cursor.moveToNext()); 2872f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 2882f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 2892f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian return ids; 2902f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 2912f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 2922f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian @TargetApi(Build.VERSION_CODES.M) // Uses try-with-resources 2932f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian private static Set<Long> getIdsFromSystemCallLogThatMatch( 2942f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian Context appContext, Set<Long> matchingIds) { 2952f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian ArraySet<Long> ids = new ArraySet<>(); 2962f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 2972f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian String[] questionMarks = new String[matchingIds.size()]; 2982f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian Arrays.fill(questionMarks, "?"); 2992f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian String whereClause = (Calls._ID + " in (") + TextUtils.join(",", questionMarks) + ")"; 3002f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian String[] whereArgs = new String[matchingIds.size()]; 3012f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian int i = 0; 3022f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian for (long id : matchingIds) { 3032f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian whereArgs[i++] = String.valueOf(id); 3042f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 3052f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 3062f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian try (Cursor cursor = 3072f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian appContext 3082f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian .getContentResolver() 3092f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian .query(Calls.CONTENT_URI, new String[] {Calls._ID}, whereClause, whereArgs, null)) { 3102f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 3112f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian if (cursor == null) { 3122f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian LogUtil.e("SystemCallLogDataSource.getIdsFromSystemCallLog", "null cursor"); 3132f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian return ids; 3142f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 3152f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian 3162f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian if (cursor.moveToFirst()) { 3172f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian int idColumn = cursor.getColumnIndexOrThrow(Calls._ID); 3182f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian do { 3192f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian ids.add(cursor.getLong(idColumn)); 3202f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } while (cursor.moveToNext()); 3212f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 3222f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian return ids; 3232f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanian } 3248369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian } 3258369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian 3268369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian private static class CallLogObserver extends ContentObserver { 3278369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian private final Context appContext; 3288369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian private final ContentObserverCallbacks contentObserverCallbacks; 3298369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian 3308369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian CallLogObserver( 3318369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian Handler handler, Context appContext, ContentObserverCallbacks contentObserverCallbacks) { 3328369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian super(handler); 3338369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian this.appContext = appContext; 3348369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian this.contentObserverCallbacks = contentObserverCallbacks; 3358369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian } 3368369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian 3378369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian @MainThread 3388369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian @Override 3398369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian public void onChange(boolean selfChange, Uri uri) { 3408369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian Assert.isMainThread(); 3418369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian LogUtil.enterBlock("SystemCallLogDataSource.CallLogObserver.onChange"); 3428369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian super.onChange(selfChange, uri); 3438369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian 3448369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian /* 3458369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * The system call log has a last updated timestamp, but deletes are physical (the "deleted" 3468369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * column is unused). This means that we can't detect deletes without scanning the entire 3478369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * table, which would be too slow. So, we just rely on content observers to trigger rebuilds 3488369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian * when any change is made to the system call log. 3498369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian */ 3508369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian contentObserverCallbacks.markDirtyAndNotify(appContext); 3518369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian } 3528369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian } 3538369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian} 354