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