• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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