• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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 package com.android.dialer.oem;
17 
18 import android.annotation.TargetApi;
19 import android.content.Context;
20 import android.content.pm.PackageManager;
21 import android.database.Cursor;
22 import android.net.Uri;
23 import android.os.Build.VERSION_CODES;
24 import android.support.annotation.AnyThread;
25 import android.support.annotation.NonNull;
26 import android.support.annotation.Nullable;
27 import android.support.annotation.WorkerThread;
28 import android.telephony.PhoneNumberUtils;
29 import android.text.TextUtils;
30 import com.android.dialer.common.Assert;
31 import com.android.dialer.common.LogUtil;
32 import com.android.dialer.configprovider.ConfigProviderBindings;
33 import java.util.concurrent.ConcurrentHashMap;
34 
35 /**
36  * Cequint Caller ID manager to provide caller information.
37  *
38  * <p>This is only enabled on Motorola devices for Sprint.
39  *
40  * <p>If it's enabled, this class will be called by call log and incall to get caller info from
41  * Cequint Caller ID. It also caches any information fetched in static map, which lives through
42  * whole application lifecycle.
43  */
44 @TargetApi(VERSION_CODES.M)
45 public class CequintCallerIdManager {
46 
47   private static final String CONFIG_CALLER_ID_ENABLED = "config_caller_id_enabled";
48 
49   private static final int CALLER_ID_LOOKUP_USER_PROVIDED_CID = 0x0001;
50   private static final int CALLER_ID_LOOKUP_SYSTEM_PROVIDED_CID = 0x0002;
51   private static final int CALLER_ID_LOOKUP_INCOMING_CALL = 0x0020;
52 
53   private static final String[] EMPTY_PROJECTION = new String[] {};
54 
55   // Column names in Cequint provider.
56   private static final String CITY_NAME = "cid_pCityName";
57   private static final String STATE_NAME = "cid_pStateName";
58   private static final String STATE_ABBR = "cid_pStateAbbr";
59   private static final String COUNTRY_NAME = "cid_pCountryName";
60   private static final String COMPANY = "cid_pCompany";
61   private static final String NAME = "cid_pName";
62   private static final String FIRST_NAME = "cid_pFirstName";
63   private static final String LAST_NAME = "cid_pLastName";
64   private static final String IMAGE = "cid_pLogo";
65   private static final String DISPLAY_NAME = "cid_pDisplayName";
66 
67   private static boolean hasAlreadyCheckedCequintCallerIdPackage;
68   private static String cequintProviderAuthority;
69 
70   // TODO(wangqi): Revisit it and maybe remove it if it's not necessary.
71   private final ConcurrentHashMap<String, CequintCallerIdContact> callLogCache;
72 
73   /** Cequint caller id contact information. */
74   public static class CequintCallerIdContact {
75     public final String name;
76     public final String geoDescription;
77     public final String imageUrl;
78 
CequintCallerIdContact(String name, String geoDescription, String imageUrl)79     private CequintCallerIdContact(String name, String geoDescription, String imageUrl) {
80       this.name = name;
81       this.geoDescription = geoDescription;
82       this.imageUrl = imageUrl;
83     }
84   }
85 
86   /** Check whether Cequint Caller Id provider package is available and enabled. */
87   @AnyThread
isCequintCallerIdEnabled(@onNull Context context)88   public static synchronized boolean isCequintCallerIdEnabled(@NonNull Context context) {
89     if (!ConfigProviderBindings.get(context).getBoolean(CONFIG_CALLER_ID_ENABLED, true)) {
90       return false;
91     }
92     if (!hasAlreadyCheckedCequintCallerIdPackage) {
93       hasAlreadyCheckedCequintCallerIdPackage = true;
94 
95       String[] providerNames = context.getResources().getStringArray(R.array.cequint_providers);
96       PackageManager packageManager = context.getPackageManager();
97       for (String provider : providerNames) {
98         if (CequintPackageUtils.isCallerIdInstalled(packageManager, provider)) {
99           cequintProviderAuthority = provider;
100           LogUtil.i(
101               "CequintCallerIdManager.isCequintCallerIdEnabled", "found provider: %s", provider);
102           return true;
103         }
104       }
105       LogUtil.d("CequintCallerIdManager.isCequintCallerIdEnabled", "no provider found");
106     }
107     return cequintProviderAuthority != null;
108   }
109 
createInstanceForCallLog()110   public static CequintCallerIdManager createInstanceForCallLog() {
111     return new CequintCallerIdManager();
112   }
113 
114   @WorkerThread
115   @Nullable
getCequintCallerIdContactForInCall( Context context, String number, String cnapName, boolean isIncoming)116   public static CequintCallerIdContact getCequintCallerIdContactForInCall(
117       Context context, String number, String cnapName, boolean isIncoming) {
118     Assert.isWorkerThread();
119     LogUtil.d(
120         "CequintCallerIdManager.getCequintCallerIdContactForInCall",
121         "number: %s, cnapName: %s, isIncoming: %b",
122         LogUtil.sanitizePhoneNumber(number),
123         LogUtil.sanitizePii(cnapName),
124         isIncoming);
125     int flag = 0;
126     if (isIncoming) {
127       flag |= CALLER_ID_LOOKUP_INCOMING_CALL;
128       flag |= CALLER_ID_LOOKUP_SYSTEM_PROVIDED_CID;
129     } else {
130       flag |= CALLER_ID_LOOKUP_USER_PROVIDED_CID;
131     }
132     String[] flags = {cnapName, String.valueOf(flag)};
133     return lookup(context, getIncallLookupUri(), number, flags);
134   }
135 
136   @WorkerThread
137   @Nullable
getCequintCallerIdContact(Context context, String number)138   public CequintCallerIdContact getCequintCallerIdContact(Context context, String number) {
139     Assert.isWorkerThread();
140     LogUtil.d(
141         "CequintCallerIdManager.getCequintCallerIdContact",
142         "number: %s",
143         LogUtil.sanitizePhoneNumber(number));
144     if (callLogCache.containsKey(number)) {
145       return callLogCache.get(number);
146     }
147     CequintCallerIdContact cequintCallerIdContact =
148         lookup(
149             context,
150             getLookupUri(),
151             PhoneNumberUtils.stripSeparators(number),
152             new String[] {"system"});
153     if (cequintCallerIdContact != null) {
154       callLogCache.put(number, cequintCallerIdContact);
155     }
156     return cequintCallerIdContact;
157   }
158 
159   @WorkerThread
160   @Nullable
lookup( Context context, Uri uri, @NonNull String number, String[] flags)161   private static CequintCallerIdContact lookup(
162       Context context, Uri uri, @NonNull String number, String[] flags) {
163     Assert.isWorkerThread();
164     Assert.isNotNull(number);
165 
166     // Cequint is using custom arguments for content provider. See more details in a bug.
167     try (Cursor cursor =
168         context.getContentResolver().query(uri, EMPTY_PROJECTION, number, flags, null)) {
169       if (cursor != null && cursor.moveToFirst()) {
170         String city = getString(cursor, cursor.getColumnIndex(CITY_NAME));
171         String state = getString(cursor, cursor.getColumnIndex(STATE_NAME));
172         String stateAbbr = getString(cursor, cursor.getColumnIndex(STATE_ABBR));
173         String country = getString(cursor, cursor.getColumnIndex(COUNTRY_NAME));
174         String company = getString(cursor, cursor.getColumnIndex(COMPANY));
175         String name = getString(cursor, cursor.getColumnIndex(NAME));
176         String firstName = getString(cursor, cursor.getColumnIndex(FIRST_NAME));
177         String lastName = getString(cursor, cursor.getColumnIndex(LAST_NAME));
178         String imageUrl = getString(cursor, cursor.getColumnIndex(IMAGE));
179         String displayName = getString(cursor, cursor.getColumnIndex(DISPLAY_NAME));
180 
181         String contactName =
182             TextUtils.isEmpty(displayName)
183                 ? generateDisplayName(firstName, lastName, company, name)
184                 : displayName;
185         String geoDescription = getGeoDescription(city, state, stateAbbr, country);
186         LogUtil.d(
187             "CequintCallerIdManager.lookup",
188             "number: %s, contact name: %s, geo: %s, photo url: %s",
189             LogUtil.sanitizePhoneNumber(number),
190             LogUtil.sanitizePii(contactName),
191             LogUtil.sanitizePii(geoDescription),
192             imageUrl);
193         return new CequintCallerIdContact(contactName, geoDescription, imageUrl);
194       } else {
195         LogUtil.d("CequintCallerIdManager.lookup", "No CequintCallerIdContact found");
196         return null;
197       }
198     } catch (Exception e) {
199       LogUtil.e("CequintCallerIdManager.lookup", "exception on query", e);
200       return null;
201     }
202   }
203 
getString(Cursor cursor, int columnIndex)204   private static String getString(Cursor cursor, int columnIndex) {
205     if (!cursor.isNull(columnIndex)) {
206       String string = cursor.getString(columnIndex);
207       if (!TextUtils.isEmpty(string)) {
208         return string;
209       }
210     }
211     return null;
212   }
213 
214   /**
215    * Returns generated name from other names, e.g. first name, last name etc. Returns null if there
216    * is no other names.
217    */
218   @Nullable
generateDisplayName( String firstName, String lastName, String company, String name)219   private static String generateDisplayName(
220       String firstName, String lastName, String company, String name) {
221     boolean hasFirstName = !TextUtils.isEmpty(firstName);
222     boolean hasLastName = !TextUtils.isEmpty(lastName);
223     boolean hasCompanyName = !TextUtils.isEmpty(company);
224     boolean hasName = !TextUtils.isEmpty(name);
225 
226     StringBuilder stringBuilder = new StringBuilder();
227 
228     if (hasFirstName || hasLastName) {
229       if (hasFirstName) {
230         stringBuilder.append(firstName);
231         if (hasLastName) {
232           stringBuilder.append(" ");
233         }
234       }
235       if (hasLastName) {
236         stringBuilder.append(lastName);
237       }
238     } else if (hasCompanyName) {
239       stringBuilder.append(company);
240     } else if (hasName) {
241       stringBuilder.append(name);
242     } else {
243       return null;
244     }
245 
246     if (stringBuilder.length() > 0) {
247       return stringBuilder.toString();
248     }
249     return null;
250   }
251 
252   /** Returns geo location information. e.g. Mountain View, CA. */
getGeoDescription( String city, String state, String stateAbbr, String country)253   private static String getGeoDescription(
254       String city, String state, String stateAbbr, String country) {
255     String geoDescription = null;
256 
257     if (TextUtils.isEmpty(city) && !TextUtils.isEmpty(state)) {
258       geoDescription = state;
259     } else if (!TextUtils.isEmpty(city) && !TextUtils.isEmpty(stateAbbr)) {
260       geoDescription = city + ", " + stateAbbr;
261     } else if (!TextUtils.isEmpty(country)) {
262       geoDescription = country;
263     }
264     return geoDescription;
265   }
266 
getLookupUri()267   private static Uri getLookupUri() {
268     return Uri.parse("content://" + cequintProviderAuthority + "/lookup");
269   }
270 
getIncallLookupUri()271   private static Uri getIncallLookupUri() {
272     return Uri.parse("content://" + cequintProviderAuthority + "/incalllookup");
273   }
274 
CequintCallerIdManager()275   private CequintCallerIdManager() {
276     callLogCache = new ConcurrentHashMap<>();
277   }
278 }
279