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.cnap;
18 
19 import android.content.Context;
20 import android.database.Cursor;
21 import android.telecom.Call;
22 import android.text.TextUtils;
23 import com.android.dialer.DialerPhoneNumber;
24 import com.android.dialer.common.Assert;
25 import com.android.dialer.common.LogUtil;
26 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
27 import com.android.dialer.common.database.Selection;
28 import com.android.dialer.inject.ApplicationContext;
29 import com.android.dialer.phonelookup.PhoneLookup;
30 import com.android.dialer.phonelookup.PhoneLookupInfo;
31 import com.android.dialer.phonelookup.PhoneLookupInfo.CnapInfo;
32 import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory;
33 import com.google.common.collect.ImmutableMap;
34 import com.google.common.collect.ImmutableSet;
35 import com.google.common.util.concurrent.Futures;
36 import com.google.common.util.concurrent.ListenableFuture;
37 import com.google.common.util.concurrent.ListeningExecutorService;
38 import com.google.protobuf.InvalidProtocolBufferException;
39 import javax.inject.Inject;
40 
41 /** PhoneLookup implementation for CNAP info. */
42 public final class CnapPhoneLookup implements PhoneLookup<CnapInfo> {
43 
44   private final Context appContext;
45   private final ListeningExecutorService backgroundExecutorService;
46 
47   @Inject
CnapPhoneLookup( @pplicationContext Context appContext, @BackgroundExecutor ListeningExecutorService backgroundExecutorService)48   CnapPhoneLookup(
49       @ApplicationContext Context appContext,
50       @BackgroundExecutor ListeningExecutorService backgroundExecutorService) {
51     this.appContext = appContext;
52     this.backgroundExecutorService = backgroundExecutorService;
53   }
54 
55   /**
56    * Override the default implementation in {@link PhoneLookup#lookup(Context, Call)} as CNAP info
57    * is in the provided {@link Call}.
58    */
59   @Override
lookup(Context appContext, Call call)60   public ListenableFuture<CnapInfo> lookup(Context appContext, Call call) {
61     String callerDisplayName = call.getDetails().getCallerDisplayName();
62     return Futures.immediateFuture(
63         TextUtils.isEmpty(callerDisplayName)
64             ? CnapInfo.getDefaultInstance()
65             : CnapInfo.newBuilder().setName(callerDisplayName).build());
66   }
67 
68   /**
69    * CNAP info cannot be retrieved when all we have is a number. The best we can do is returning the
70    * existing info in {@link PhoneLookupHistory}.
71    */
72   @Override
lookup(DialerPhoneNumber dialerPhoneNumber)73   public ListenableFuture<CnapInfo> lookup(DialerPhoneNumber dialerPhoneNumber) {
74     return backgroundExecutorService.submit(
75         () -> {
76           Selection selection =
77               Selection.builder()
78                   .and(
79                       Selection.column(PhoneLookupHistory.NORMALIZED_NUMBER)
80                           .is("=", dialerPhoneNumber.getNormalizedNumber()))
81                   .build();
82 
83           try (Cursor cursor =
84               appContext
85                   .getContentResolver()
86                   .query(
87                       PhoneLookupHistory.CONTENT_URI,
88                       new String[] {PhoneLookupHistory.PHONE_LOOKUP_INFO},
89                       selection.getSelection(),
90                       selection.getSelectionArgs(),
91                       /* sortOrder = */ null)) {
92             if (cursor == null) {
93               LogUtil.e("CnapPhoneLookup.lookup", "null cursor");
94               return CnapInfo.getDefaultInstance();
95             }
96 
97             if (!cursor.moveToFirst()) {
98               LogUtil.i("CnapPhoneLookup.lookup", "empty cursor");
99               return CnapInfo.getDefaultInstance();
100             }
101 
102             // At ths point, we expect only one row in the cursor as
103             // PhoneLookupHistory.NORMALIZED_NUMBER is the primary key of table PhoneLookupHistory.
104             Assert.checkState(cursor.getCount() == 1);
105 
106             int phoneLookupInfoColumn =
107                 cursor.getColumnIndexOrThrow(PhoneLookupHistory.PHONE_LOOKUP_INFO);
108             PhoneLookupInfo phoneLookupInfo;
109             try {
110               phoneLookupInfo = PhoneLookupInfo.parseFrom(cursor.getBlob(phoneLookupInfoColumn));
111             } catch (InvalidProtocolBufferException e) {
112               throw new IllegalStateException(e);
113             }
114 
115             return phoneLookupInfo.getCnapInfo();
116           }
117         });
118   }
119 
120   @Override
isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers)121   public ListenableFuture<Boolean> isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers) {
122     return Futures.immediateFuture(false);
123   }
124 
125   @Override
getMostRecentInfo( ImmutableMap<DialerPhoneNumber, CnapInfo> existingInfoMap)126   public ListenableFuture<ImmutableMap<DialerPhoneNumber, CnapInfo>> getMostRecentInfo(
127       ImmutableMap<DialerPhoneNumber, CnapInfo> existingInfoMap) {
128     return Futures.immediateFuture(existingInfoMap);
129   }
130 
131   @Override
setSubMessage(PhoneLookupInfo.Builder destination, CnapInfo subMessage)132   public void setSubMessage(PhoneLookupInfo.Builder destination, CnapInfo subMessage) {
133     destination.setCnapInfo(subMessage);
134   }
135 
136   @Override
getSubMessage(PhoneLookupInfo phoneLookupInfo)137   public CnapInfo getSubMessage(PhoneLookupInfo phoneLookupInfo) {
138     return phoneLookupInfo.getCnapInfo();
139   }
140 
141   @Override
onSuccessfulBulkUpdate()142   public ListenableFuture<Void> onSuccessfulBulkUpdate() {
143     return Futures.immediateFuture(null);
144   }
145 
146   @Override
registerContentObservers()147   public void registerContentObservers() {
148     // No content observers for CNAP info.
149   }
150 
151   @Override
unregisterContentObservers()152   public void unregisterContentObservers() {
153     // No content observers for CNAP info.
154   }
155 
156   @Override
clearData()157   public ListenableFuture<Void> clearData() {
158     return Futures.immediateFuture(null);
159   }
160 
161   @Override
getLoggingName()162   public String getLoggingName() {
163     return "CnapPhoneLookup";
164   }
165 }
166