Home | History | Annotate | Download | only in oem
      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