1 /* 2 * Copyright (C) 2018 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.dialer.phonelookup.cp2; 18 19 import android.content.Context; 20 import android.database.Cursor; 21 import com.android.dialer.DialerPhoneNumber; 22 import com.android.dialer.common.LogUtil; 23 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; 24 import com.android.dialer.common.concurrent.Annotations.LightweightExecutor; 25 import com.android.dialer.common.database.Selection; 26 import com.android.dialer.inject.ApplicationContext; 27 import com.android.dialer.phonelookup.PhoneLookupInfo; 28 import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info; 29 import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory; 30 import com.google.common.collect.ImmutableMap; 31 import com.google.common.collect.ImmutableSet; 32 import com.google.common.util.concurrent.ListenableFuture; 33 import com.google.common.util.concurrent.ListeningExecutorService; 34 import com.google.protobuf.InvalidProtocolBufferException; 35 import java.util.function.Predicate; 36 import javax.inject.Inject; 37 38 /** Shared logic for handling missing permissions in CP2 lookups. */ 39 final class MissingPermissionsOperations { 40 41 private final Context appContext; 42 private final ListeningExecutorService backgroundExecutor; 43 private final ListeningExecutorService lightweightExecutor; 44 45 @Inject MissingPermissionsOperations( @pplicationContext Context appContext, @BackgroundExecutor ListeningExecutorService backgroundExecutor, @LightweightExecutor ListeningExecutorService lightweightExecutor)46 MissingPermissionsOperations( 47 @ApplicationContext Context appContext, 48 @BackgroundExecutor ListeningExecutorService backgroundExecutor, 49 @LightweightExecutor ListeningExecutorService lightweightExecutor) { 50 this.appContext = appContext; 51 this.backgroundExecutor = backgroundExecutor; 52 this.lightweightExecutor = lightweightExecutor; 53 } 54 55 /** 56 * Returns true if there is any CP2 data for the specified numbers in PhoneLookupHistory, because 57 * that data needs to be cleared. 58 * 59 * <p>Note: This might be a little slow for users without contacts permissions, but we don't 60 * expect this to often be the case. If necessary, a shared pref could be used to track the 61 * permission state as an optimization. 62 */ isDirtyForMissingPermissions( ImmutableSet<DialerPhoneNumber> phoneNumbers, Predicate<PhoneLookupInfo> phoneLookupInfoIsDirtyFn)63 ListenableFuture<Boolean> isDirtyForMissingPermissions( 64 ImmutableSet<DialerPhoneNumber> phoneNumbers, 65 Predicate<PhoneLookupInfo> phoneLookupInfoIsDirtyFn) { 66 return backgroundExecutor.submit( 67 () -> { 68 // Note: This loses country info when number is not valid. 69 String[] normalizedNumbers = 70 phoneNumbers 71 .stream() 72 .map(DialerPhoneNumber::getNormalizedNumber) 73 .toArray(String[]::new); 74 75 Selection selection = 76 Selection.builder() 77 .and(Selection.column(PhoneLookupHistory.NORMALIZED_NUMBER).in(normalizedNumbers)) 78 .build(); 79 80 try (Cursor cursor = 81 appContext 82 .getContentResolver() 83 .query( 84 PhoneLookupHistory.CONTENT_URI, 85 new String[] { 86 PhoneLookupHistory.PHONE_LOOKUP_INFO, 87 }, 88 selection.getSelection(), 89 selection.getSelectionArgs(), 90 null)) { 91 92 if (cursor == null) { 93 LogUtil.w("MissingPermissionsOperations.isDirtyForMissingPermissions", "null cursor"); 94 return false; 95 } 96 97 if (cursor.moveToFirst()) { 98 int phoneLookupInfoColumn = 99 cursor.getColumnIndexOrThrow(PhoneLookupHistory.PHONE_LOOKUP_INFO); 100 do { 101 PhoneLookupInfo phoneLookupInfo; 102 try { 103 phoneLookupInfo = 104 PhoneLookupInfo.parseFrom(cursor.getBlob(phoneLookupInfoColumn)); 105 } catch (InvalidProtocolBufferException e) { 106 throw new IllegalStateException(e); 107 } 108 if (phoneLookupInfoIsDirtyFn.test(phoneLookupInfo)) { 109 return true; 110 } 111 } while (cursor.moveToNext()); 112 } 113 } 114 return false; 115 }); 116 } 117 118 /** Clears all CP2 info because permissions are missing. */ getMostRecentInfoForMissingPermissions( ImmutableMap<DialerPhoneNumber, Cp2Info> existingInfoMap)119 ListenableFuture<ImmutableMap<DialerPhoneNumber, Cp2Info>> getMostRecentInfoForMissingPermissions( 120 ImmutableMap<DialerPhoneNumber, Cp2Info> existingInfoMap) { 121 return lightweightExecutor.submit( 122 () -> { 123 ImmutableMap.Builder<DialerPhoneNumber, Cp2Info> clearedInfos = ImmutableMap.builder(); 124 for (DialerPhoneNumber number : existingInfoMap.keySet()) { 125 clearedInfos.put(number, Cp2Info.getDefaultInstance()); 126 } 127 return clearedInfos.build(); 128 }); 129 } 130 } 131