summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/oem/CequintCallerIdManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialer/oem/CequintCallerIdManager.java')
-rw-r--r--java/com/android/dialer/oem/CequintCallerIdManager.java297
1 files changed, 297 insertions, 0 deletions
diff --git a/java/com/android/dialer/oem/CequintCallerIdManager.java b/java/com/android/dialer/oem/CequintCallerIdManager.java
new file mode 100644
index 000000000..094cc842d
--- /dev/null
+++ b/java/com/android/dialer/oem/CequintCallerIdManager.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dialer.oem;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build.VERSION_CODES;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.ConfigProviderBindings;
+import com.android.dialer.common.LogUtil;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Cequint Caller ID manager to provide caller information.
+ *
+ * <p>This is only enabled on Motorola devices for Sprint.
+ *
+ * <p>If it's enabled, this class will be called by call log and incall to get caller info from
+ * Cequint Caller ID. It also caches any information fetched in static map, which lives through
+ * whole application lifecycle.
+ */
+@TargetApi(VERSION_CODES.M)
+public class CequintCallerIdManager {
+
+ private static final String CONFIG_CALLER_ID_ENABLED = "config_caller_id_enabled";
+
+ private static final String PROVIDER_NAME = "com.cequint.ecid";
+
+ private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/lookup");
+
+ private static final int CALLER_ID_LOOKUP_USER_PROVIDED_CID = 0x0001;
+ private static final int CALLER_ID_LOOKUP_SYSTEM_PROVIDED_CID = 0x0002;
+ private static final int CALLER_ID_LOOKUP_INCOMING_CALL = 0x0020;
+
+ private static final Uri CONTENT_URI_FOR_INCALL =
+ Uri.parse("content://" + PROVIDER_NAME + "/incalllookup");
+
+ // Column names in Cequint provider.
+ private static final String CITY_NAME = "cid_pCityName";
+ private static final String STATE_NAME = "cid_pStateName";
+ private static final String STATE_ABBR = "cid_pStateAbbr";
+ private static final String COUNTRY_NAME = "cid_pCountryName";
+ private static final String COMPANY = "cid_pCompany";
+ private static final String NAME = "cid_pName";
+ private static final String FIRST_NAME = "cid_pFirstName";
+ private static final String LAST_NAME = "cid_pLastName";
+ private static final String IMAGE = "cid_pImage";
+ private static final String DISPLAY_NAME = "cid_pDisplayName";
+
+ // TODO: Revisit it and maybe remove it if it's not necessary.
+ private static final ConcurrentHashMap<String, CequintCallerIdContact> callLogCache =
+ new ConcurrentHashMap<>();
+ // Cache for incall lookup. Key is phone number + "i" for incoming call or "o" for outgoing call.
+ // TODO: Revisit it and maybe remove it if it's not necessary.
+ private static final ConcurrentHashMap<String, CequintCallerIdContact> incallCache =
+ new ConcurrentHashMap<>();
+ private static boolean hasRegisteredContentObserver;
+ private static boolean hasAlreadyCheckedCequintCallerIdPackage;
+ private static boolean isCequintCallerIdEnabled;
+
+ /** Cequint caller id contact information. */
+ public static class CequintCallerIdContact {
+ public final String name;
+ public final String geoDescription;
+ public final String imageUrl;
+
+ private CequintCallerIdContact(String name, String geoDescription, String imageUrl) {
+ this.name = name;
+ this.geoDescription = geoDescription;
+ this.imageUrl = imageUrl;
+ }
+ }
+
+ /** Check whether Cequint Caller Id provider package is available and enabled. */
+ public static synchronized boolean isCequintCallerIdEnabled(@NonNull Context context) {
+ if (!ConfigProviderBindings.get(context).getBoolean(CONFIG_CALLER_ID_ENABLED, true)) {
+ return false;
+ }
+ if (!hasAlreadyCheckedCequintCallerIdPackage) {
+ hasAlreadyCheckedCequintCallerIdPackage = true;
+ isCequintCallerIdEnabled = false;
+
+ try {
+ context.getPackageManager().getPackageInfo(PROVIDER_NAME, 0);
+ isCequintCallerIdEnabled = true;
+ } catch (PackageManager.NameNotFoundException e) {
+ isCequintCallerIdEnabled = false;
+ }
+ }
+ return isCequintCallerIdEnabled;
+ }
+
+ @WorkerThread
+ public static CequintCallerIdContact getCequintCallerIdContact(Context context, String number) {
+ Assert.isWorkerThread();
+ LogUtil.d(
+ "CequintCallerIdManager.getCequintCallerIdContact",
+ "number: %s",
+ LogUtil.sanitizePhoneNumber(number));
+ registerContentObserver(context);
+ if (callLogCache.containsKey(number)) {
+ return callLogCache.get(number);
+ }
+ CequintCallerIdContact cequintCallerIdContact =
+ lookup(
+ context,
+ CONTENT_URI,
+ PhoneNumberUtils.stripSeparators(number),
+ new String[] {"system"});
+ if (cequintCallerIdContact != null) {
+ callLogCache.put(number, cequintCallerIdContact);
+ }
+ return cequintCallerIdContact;
+ }
+
+ @WorkerThread
+ public static CequintCallerIdContact getCequintCallerIdContactForInCall(
+ Context context, String number, String cnapName, boolean isIncoming) {
+ Assert.isWorkerThread();
+ LogUtil.d(
+ "CequintCallerIdManager.getCequintCallerIdContactForInCall",
+ "number: %s, cnapName: %s, isIncoming: %b",
+ LogUtil.sanitizePhoneNumber(number),
+ LogUtil.sanitizePii(cnapName),
+ isIncoming);
+ registerContentObserver(context);
+ String key = number + (isIncoming ? "i" : "o");
+ if (incallCache.containsKey(key)) {
+ return incallCache.get(key);
+ }
+ int flag = 0;
+ if (isIncoming) {
+ flag |= CALLER_ID_LOOKUP_INCOMING_CALL;
+ flag |= CALLER_ID_LOOKUP_SYSTEM_PROVIDED_CID;
+ } else {
+ flag |= CALLER_ID_LOOKUP_USER_PROVIDED_CID;
+ }
+ String[] flags = {cnapName, String.valueOf(flag)};
+ CequintCallerIdContact cequintCallerIdContact =
+ lookup(context, CONTENT_URI_FOR_INCALL, number, flags);
+ if (cequintCallerIdContact != null) {
+ incallCache.put(key, cequintCallerIdContact);
+ }
+ return cequintCallerIdContact;
+ }
+
+ @WorkerThread
+ @Nullable
+ private static CequintCallerIdContact lookup(
+ Context context, Uri uri, String number, String[] flags) {
+ Assert.isWorkerThread();
+
+ // Cequint is using custom arguments for content provider. See more details in b/35766080.
+ try (Cursor cursor = context.getContentResolver().query(uri, null, number, flags, null)) {
+ if (cursor != null && cursor.moveToFirst()) {
+ String city = getString(cursor, cursor.getColumnIndex(CITY_NAME));
+ String state = getString(cursor, cursor.getColumnIndex(STATE_NAME));
+ String stateAbbr = getString(cursor, cursor.getColumnIndex(STATE_ABBR));
+ String country = getString(cursor, cursor.getColumnIndex(COUNTRY_NAME));
+ String company = getString(cursor, cursor.getColumnIndex(COMPANY));
+ String name = getString(cursor, cursor.getColumnIndex(NAME));
+ String firstName = getString(cursor, cursor.getColumnIndex(FIRST_NAME));
+ String lastName = getString(cursor, cursor.getColumnIndex(LAST_NAME));
+ String imageUrl = getString(cursor, cursor.getColumnIndex(IMAGE));
+ String displayName = getString(cursor, cursor.getColumnIndex(DISPLAY_NAME));
+
+ String contactName =
+ TextUtils.isEmpty(displayName)
+ ? generateDisplayName(firstName, lastName, company, name)
+ : displayName;
+ String geoDescription = getGeoDescription(city, state, stateAbbr, country);
+ LogUtil.d(
+ "CequintCallerIdManager.lookup",
+ "number: %s, contact name: %s, geo: %s, photo url: %s",
+ LogUtil.sanitizePhoneNumber(number),
+ LogUtil.sanitizePii(contactName),
+ LogUtil.sanitizePii(geoDescription),
+ imageUrl);
+ return new CequintCallerIdContact(contactName, geoDescription, imageUrl);
+ } else {
+ LogUtil.d("CequintCallerIdManager.lookup", "No CequintCallerIdContact found");
+ return null;
+ }
+ }
+ }
+
+ private static String getString(Cursor cursor, int columnIndex) {
+ if (!cursor.isNull(columnIndex)) {
+ String string = cursor.getString(columnIndex);
+ if (!TextUtils.isEmpty(string)) {
+ return string;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns generated name from other names, e.g. first name, last name etc. Returns null if there
+ * is no other names.
+ */
+ @Nullable
+ private static String generateDisplayName(
+ String firstName, String lastName, String company, String name) {
+ boolean hasFirstName = !TextUtils.isEmpty(firstName);
+ boolean hasLastName = !TextUtils.isEmpty(lastName);
+ boolean hasCompanyName = !TextUtils.isEmpty(company);
+ boolean hasName = !TextUtils.isEmpty(name);
+
+ StringBuilder stringBuilder = new StringBuilder();
+
+ if (hasFirstName || hasLastName) {
+ if (hasFirstName) {
+ stringBuilder.append(firstName);
+ if (hasLastName) {
+ stringBuilder.append(" ");
+ }
+ }
+ if (hasLastName) {
+ stringBuilder.append(lastName);
+ }
+ } else if (hasCompanyName) {
+ stringBuilder.append(company);
+ } else if (hasName) {
+ stringBuilder.append(name);
+ } else {
+ return null;
+ }
+
+ if (stringBuilder.length() > 0) {
+ return stringBuilder.toString();
+ }
+ return null;
+ }
+
+ /** Returns geo location information. e.g. Mountain View, CA. */
+ private static String getGeoDescription(
+ String city, String state, String stateAbbr, String country) {
+ String geoDescription = null;
+
+ if (TextUtils.isEmpty(city) && !TextUtils.isEmpty(state)) {
+ geoDescription = state;
+ } else if (!TextUtils.isEmpty(city) && !TextUtils.isEmpty(stateAbbr)) {
+ geoDescription = city + ", " + stateAbbr;
+ } else if (!TextUtils.isEmpty(country)) {
+ geoDescription = country;
+ }
+ return geoDescription;
+ }
+
+ private static void registerContentObserver(Context context) {
+ if (hasRegisteredContentObserver) {
+ return;
+ }
+ ContentObserver contentObserver =
+ new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ invalidateCache();
+ }
+ };
+
+ context.getContentResolver().registerContentObserver(CONTENT_URI, true, contentObserver);
+ context
+ .getContentResolver()
+ .registerContentObserver(CONTENT_URI_FOR_INCALL, true, contentObserver);
+ hasRegisteredContentObserver = true;
+ }
+
+ private static void invalidateCache() {
+ callLogCache.clear();
+ incallCache.clear();
+ }
+
+ private CequintCallerIdManager() {}
+}