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 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 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 110 public static CequintCallerIdManager createInstanceForCallLog() { 111 return new CequintCallerIdManager(); 112 } 113 114 @WorkerThread 115 @Nullable 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 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 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 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 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. */ 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 267 private static Uri getLookupUri() { 268 return Uri.parse("content://" + cequintProviderAuthority + "/lookup"); 269 } 270 271 private static Uri getIncallLookupUri() { 272 return Uri.parse("content://" + cequintProviderAuthority + "/incalllookup"); 273 } 274 275 private CequintCallerIdManager() { 276 callLogCache = new ConcurrentHashMap<>(); 277 } 278 } 279