Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8fb18327 authored by zachh's avatar zachh Committed by android-build-merger
Browse files

Merge "Improved support for missing contacts permission in new call log." am: c6bd3f31

am: 32e1125f

Change-Id: I30a012e486e8b51763425a4ff663baee1da4ed53
parents 5848883b 32e1125f
Loading
Loading
Loading
Loading
+24 −1
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info.Cp2ContactInfo;
import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory;
import com.android.dialer.phonenumberproto.PartitionedNumbers;
import com.android.dialer.storage.Unencrypted;
import com.android.dialer.util.PermissionsUtil;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@@ -58,9 +59,11 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Predicate;
import javax.inject.Inject;

/** PhoneLookup implementation for contacts in the default directory. */
@SuppressWarnings("AndroidApiChecker") // Use of Java 8 APIs.
public final class Cp2DefaultDirectoryPhoneLookup implements PhoneLookup<Cp2Info> {

  private static final String PREF_LAST_TIMESTAMP_PROCESSED =
@@ -71,6 +74,7 @@ public final class Cp2DefaultDirectoryPhoneLookup implements PhoneLookup<Cp2Info
  private final ListeningExecutorService backgroundExecutorService;
  private final ListeningExecutorService lightweightExecutorService;
  private final ConfigProvider configProvider;
  private final MissingPermissionsOperations missingPermissionsOperations;

  @Nullable private Long currentLastTimestampProcessed;

@@ -80,16 +84,21 @@ public final class Cp2DefaultDirectoryPhoneLookup implements PhoneLookup<Cp2Info
      @Unencrypted SharedPreferences sharedPreferences,
      @BackgroundExecutor ListeningExecutorService backgroundExecutorService,
      @LightweightExecutor ListeningExecutorService lightweightExecutorService,
      ConfigProvider configProvider) {
      ConfigProvider configProvider,
      MissingPermissionsOperations missingPermissionsOperations) {
    this.appContext = appContext;
    this.sharedPreferences = sharedPreferences;
    this.backgroundExecutorService = backgroundExecutorService;
    this.lightweightExecutorService = lightweightExecutorService;
    this.configProvider = configProvider;
    this.missingPermissionsOperations = missingPermissionsOperations;
  }

  @Override
  public ListenableFuture<Cp2Info> lookup(DialerPhoneNumber dialerPhoneNumber) {
    if (!PermissionsUtil.hasContactsReadPermissions(appContext)) {
      return Futures.immediateFuture(Cp2Info.getDefaultInstance());
    }
    return backgroundExecutorService.submit(() -> lookupInternal(dialerPhoneNumber));
  }

@@ -137,6 +146,15 @@ public final class Cp2DefaultDirectoryPhoneLookup implements PhoneLookup<Cp2Info

  @Override
  public ListenableFuture<Boolean> isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers) {
    if (!PermissionsUtil.hasContactsReadPermissions(appContext)) {
      LogUtil.w("Cp2DefaultDirectoryPhoneLookup.isDirty", "missing permissions");
      Predicate<PhoneLookupInfo> phoneLookupInfoIsDirtyFn =
          phoneLookupInfo ->
              !phoneLookupInfo.getDefaultCp2Info().equals(Cp2Info.getDefaultInstance());
      return missingPermissionsOperations.isDirtyForMissingPermissions(
          phoneNumbers, phoneLookupInfoIsDirtyFn);
    }

    PartitionedNumbers partitionedNumbers = new PartitionedNumbers(phoneNumbers);
    if (partitionedNumbers.invalidNumbers().size() > getMaxSupportedInvalidNumbers()) {
      // If there are N invalid numbers, we can't determine determine dirtiness without running N
@@ -441,6 +459,11 @@ public final class Cp2DefaultDirectoryPhoneLookup implements PhoneLookup<Cp2Info
      ImmutableMap<DialerPhoneNumber, Cp2Info> existingInfoMap) {
    currentLastTimestampProcessed = null;

    if (!PermissionsUtil.hasContactsReadPermissions(appContext)) {
      LogUtil.w("Cp2DefaultDirectoryPhoneLookup.getMostRecentInfo", "missing permissions");
      return missingPermissionsOperations.getMostRecentInfoForMissingPermissions(existingInfoMap);
    }

    ListenableFuture<Long> lastModifiedFuture =
        backgroundExecutorService.submit(
            () -> sharedPreferences.getLong(PREF_LAST_TIMESTAMP_PROCESSED, 0L));
+21 −1
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import com.android.dialer.phonelookup.PhoneLookup;
import com.android.dialer.phonelookup.PhoneLookupInfo;
import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info;
import com.android.dialer.phonenumberutil.PhoneNumberHelper;
import com.android.dialer.util.PermissionsUtil;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.Futures;
@@ -38,6 +39,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import javax.inject.Inject;

/**
@@ -46,24 +48,31 @@ import javax.inject.Inject;
 *
 * <p>Contacts in these directories are accessible only by specifying a directory ID.
 */
@SuppressWarnings("AndroidApiChecker") // Use of Java 8 APIs.
public final class Cp2ExtendedDirectoryPhoneLookup implements PhoneLookup<Cp2Info> {

  private final Context appContext;
  private final ListeningExecutorService backgroundExecutorService;
  private final ListeningExecutorService lightweightExecutorService;
  private final MissingPermissionsOperations missingPermissionsOperations;

  @Inject
  Cp2ExtendedDirectoryPhoneLookup(
      @ApplicationContext Context appContext,
      @BackgroundExecutor ListeningExecutorService backgroundExecutorService,
      @LightweightExecutor ListeningExecutorService lightweightExecutorService) {
      @LightweightExecutor ListeningExecutorService lightweightExecutorService,
      MissingPermissionsOperations missingPermissionsOperations) {
    this.appContext = appContext;
    this.backgroundExecutorService = backgroundExecutorService;
    this.lightweightExecutorService = lightweightExecutorService;
    this.missingPermissionsOperations = missingPermissionsOperations;
  }

  @Override
  public ListenableFuture<Cp2Info> lookup(DialerPhoneNumber dialerPhoneNumber) {
    if (!PermissionsUtil.hasContactsReadPermissions(appContext)) {
      return Futures.immediateFuture(Cp2Info.getDefaultInstance());
    }
    return Futures.transformAsync(
        queryCp2ForExtendedDirectoryIds(),
        directoryIds -> queryCp2ForDirectoryContact(dialerPhoneNumber, directoryIds),
@@ -196,12 +205,23 @@ public final class Cp2ExtendedDirectoryPhoneLookup implements PhoneLookup<Cp2Inf

  @Override
  public ListenableFuture<Boolean> isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers) {
    if (!PermissionsUtil.hasContactsReadPermissions(appContext)) {
      Predicate<PhoneLookupInfo> phoneLookupInfoIsDirtyFn =
          phoneLookupInfo ->
              !phoneLookupInfo.getExtendedCp2Info().equals(Cp2Info.getDefaultInstance());
      return missingPermissionsOperations.isDirtyForMissingPermissions(
          phoneNumbers, phoneLookupInfoIsDirtyFn);
    }
    return Futures.immediateFuture(false);
  }

  @Override
  public ListenableFuture<ImmutableMap<DialerPhoneNumber, Cp2Info>> getMostRecentInfo(
      ImmutableMap<DialerPhoneNumber, Cp2Info> existingInfoMap) {
    if (!PermissionsUtil.hasContactsReadPermissions(appContext)) {
      LogUtil.w("Cp2ExtendedDirectoryPhoneLookup.getMostRecentInfo", "missing permissions");
      return missingPermissionsOperations.getMostRecentInfoForMissingPermissions(existingInfoMap);
    }
    return Futures.immediateFuture(existingInfoMap);
  }

+131 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.dialer.phonelookup.cp2;

import android.content.Context;
import android.database.Cursor;
import com.android.dialer.DialerPhoneNumber;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
import com.android.dialer.common.concurrent.Annotations.LightweightExecutor;
import com.android.dialer.common.database.Selection;
import com.android.dialer.inject.ApplicationContext;
import com.android.dialer.phonelookup.PhoneLookupInfo;
import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info;
import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.protobuf.InvalidProtocolBufferException;
import java.util.function.Predicate;
import javax.inject.Inject;

/** Shared logic for handling missing permissions in CP2 lookups. */
@SuppressWarnings("AndroidApiChecker") // Use of Java 8 APIs.
final class MissingPermissionsOperations {

  private final Context appContext;
  private final ListeningExecutorService backgroundExecutor;
  private final ListeningExecutorService lightweightExecutor;

  @Inject
  MissingPermissionsOperations(
      @ApplicationContext Context appContext,
      @BackgroundExecutor ListeningExecutorService backgroundExecutor,
      @LightweightExecutor ListeningExecutorService lightweightExecutor) {
    this.appContext = appContext;
    this.backgroundExecutor = backgroundExecutor;
    this.lightweightExecutor = lightweightExecutor;
  }

  /**
   * Returns true if there is any CP2 data for the specified numbers in PhoneLookupHistory, because
   * that data needs to be cleared.
   *
   * <p>Note: This might be a little slow for users without contacts permissions, but we don't
   * expect this to often be the case. If necessary, a shared pref could be used to track the
   * permission state as an optimization.
   */
  ListenableFuture<Boolean> isDirtyForMissingPermissions(
      ImmutableSet<DialerPhoneNumber> phoneNumbers,
      Predicate<PhoneLookupInfo> phoneLookupInfoIsDirtyFn) {
    return backgroundExecutor.submit(
        () -> {
          // Note: This loses country info when number is not valid.
          String[] normalizedNumbers =
              phoneNumbers
                  .stream()
                  .map(DialerPhoneNumber::getNormalizedNumber)
                  .toArray(String[]::new);

          Selection selection =
              Selection.builder()
                  .and(Selection.column(PhoneLookupHistory.NORMALIZED_NUMBER).in(normalizedNumbers))
                  .build();

          try (Cursor cursor =
              appContext
                  .getContentResolver()
                  .query(
                      PhoneLookupHistory.CONTENT_URI,
                      new String[] {
                        PhoneLookupHistory.PHONE_LOOKUP_INFO,
                      },
                      selection.getSelection(),
                      selection.getSelectionArgs(),
                      null)) {

            if (cursor == null) {
              LogUtil.w("MissingPermissionsOperations.isDirtyForMissingPermissions", "null cursor");
              return false;
            }

            if (cursor.moveToFirst()) {
              int phoneLookupInfoColumn =
                  cursor.getColumnIndexOrThrow(PhoneLookupHistory.PHONE_LOOKUP_INFO);
              do {
                PhoneLookupInfo phoneLookupInfo;
                try {
                  phoneLookupInfo =
                      PhoneLookupInfo.parseFrom(cursor.getBlob(phoneLookupInfoColumn));
                } catch (InvalidProtocolBufferException e) {
                  throw new IllegalStateException(e);
                }
                if (phoneLookupInfoIsDirtyFn.test(phoneLookupInfo)) {
                  return true;
                }
              } while (cursor.moveToNext());
            }
          }
          return false;
        });
  }

  /** Clears all CP2 info because permissions are missing. */
  ListenableFuture<ImmutableMap<DialerPhoneNumber, Cp2Info>> getMostRecentInfoForMissingPermissions(
      ImmutableMap<DialerPhoneNumber, Cp2Info> existingInfoMap) {
    return lightweightExecutor.submit(
        () -> {
          ImmutableMap.Builder<DialerPhoneNumber, Cp2Info> clearedInfos = ImmutableMap.builder();
          for (DialerPhoneNumber number : existingInfoMap.keySet()) {
            clearedInfos.put(number, Cp2Info.getDefaultInstance());
          }
          return clearedInfos.build();
        });
  }
}
+2 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.dialer.phonelookup.database;

import android.content.Context;
import com.android.dialer.inject.HasRootComponent;
import com.android.dialer.inject.IncludeInDialerRoot;
import dagger.Subcomponent;

/** Dagger component for database package. */
@@ -32,6 +33,7 @@ public abstract class PhoneLookupDatabaseComponent {
  }

  /** Used to refer to the root application component. */
  @IncludeInDialerRoot
  public interface HasComponent {
    PhoneLookupDatabaseComponent phoneLookupDatabaseComponent();
  }