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 String PROVIDER_NAME = "com.cequint.ecid";
     50 
     51   private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/lookup");
     52 
     53   private static final int CALLER_ID_LOOKUP_USER_PROVIDED_CID = 0x0001;
     54   private static final int CALLER_ID_LOOKUP_SYSTEM_PROVIDED_CID = 0x0002;
     55   private static final int CALLER_ID_LOOKUP_INCOMING_CALL = 0x0020;
     56 
     57   private static final Uri CONTENT_URI_FOR_INCALL =
     58       Uri.parse("content://" + PROVIDER_NAME + "/incalllookup");
     59 
     60   private static final String[] EMPTY_PROJECTION = new String[] {};
     61 
     62   // Column names in Cequint provider.
     63   private static final String CITY_NAME = "cid_pCityName";
     64   private static final String STATE_NAME = "cid_pStateName";
     65   private static final String STATE_ABBR = "cid_pStateAbbr";
     66   private static final String COUNTRY_NAME = "cid_pCountryName";
     67   private static final String COMPANY = "cid_pCompany";
     68   private static final String NAME = "cid_pName";
     69   private static final String FIRST_NAME = "cid_pFirstName";
     70   private static final String LAST_NAME = "cid_pLastName";
     71   private static final String IMAGE = "cid_pLogo";
     72   private static final String DISPLAY_NAME = "cid_pDisplayName";
     73 
     74   private static boolean hasAlreadyCheckedCequintCallerIdPackage;
     75   private static boolean isCequintCallerIdEnabled;
     76 
     77   // TODO: Revisit it and maybe remove it if it's not necessary.
     78   private final ConcurrentHashMap<String, CequintCallerIdContact> callLogCache;
     79 
     80   /** Cequint caller id contact information. */
     81   public static class CequintCallerIdContact {
     82     public final String name;
     83     public final String geoDescription;
     84     public final String imageUrl;
     85 
     86     private CequintCallerIdContact(String name, String geoDescription, String imageUrl) {
     87       this.name = name;
     88       this.geoDescription = geoDescription;
     89       this.imageUrl = imageUrl;
     90     }
     91   }
     92 
     93   /** Check whether Cequint Caller Id provider package is available and enabled. */
     94   @AnyThread
     95   public static synchronized boolean isCequintCallerIdEnabled(@NonNull Context context) {
     96     if (!ConfigProviderBindings.get(context).getBoolean(CONFIG_CALLER_ID_ENABLED, true)) {
     97       return false;
     98     }
     99     if (!hasAlreadyCheckedCequintCallerIdPackage) {
    100       hasAlreadyCheckedCequintCallerIdPackage = true;
    101       isCequintCallerIdEnabled = false;
    102 
    103       try {
    104         context.getPackageManager().getPackageInfo(PROVIDER_NAME, 0);
    105         isCequintCallerIdEnabled = true;
    106       } catch (PackageManager.NameNotFoundException e) {
    107         isCequintCallerIdEnabled = false;
    108       }
    109     }
    110     return isCequintCallerIdEnabled;
    111   }
    112 
    113   public static CequintCallerIdManager createInstanceForCallLog() {
    114     return new CequintCallerIdManager();
    115   }
    116 
    117   @WorkerThread
    118   @Nullable
    119   public static CequintCallerIdContact getCequintCallerIdContactForInCall(
    120       Context context, String number, String cnapName, boolean isIncoming) {
    121     Assert.isWorkerThread();
    122     LogUtil.d(
    123         "CequintCallerIdManager.getCequintCallerIdContactForInCall",
    124         "number: %s, cnapName: %s, isIncoming: %b",
    125         LogUtil.sanitizePhoneNumber(number),
    126         LogUtil.sanitizePii(cnapName),
    127         isIncoming);
    128     int flag = 0;
    129     if (isIncoming) {
    130       flag |= CALLER_ID_LOOKUP_INCOMING_CALL;
    131       flag |= CALLER_ID_LOOKUP_SYSTEM_PROVIDED_CID;
    132     } else {
    133       flag |= CALLER_ID_LOOKUP_USER_PROVIDED_CID;
    134     }
    135     String[] flags = {cnapName, String.valueOf(flag)};
    136     return lookup(context, CONTENT_URI_FOR_INCALL, number, flags);
    137   }
    138 
    139   @WorkerThread
    140   @Nullable
    141   public CequintCallerIdContact getCequintCallerIdContact(Context context, String number) {
    142     Assert.isWorkerThread();
    143     LogUtil.d(
    144         "CequintCallerIdManager.getCequintCallerIdContact",
    145         "number: %s",
    146         LogUtil.sanitizePhoneNumber(number));
    147     if (callLogCache.containsKey(number)) {
    148       return callLogCache.get(number);
    149     }
    150     CequintCallerIdContact cequintCallerIdContact =
    151         lookup(
    152             context,
    153             CONTENT_URI,
    154             PhoneNumberUtils.stripSeparators(number),
    155             new String[] {"system"});
    156     if (cequintCallerIdContact != null) {
    157       callLogCache.put(number, cequintCallerIdContact);
    158     }
    159     return cequintCallerIdContact;
    160   }
    161 
    162   @WorkerThread
    163   @Nullable
    164   private static CequintCallerIdContact lookup(
    165       Context context, Uri uri, @NonNull String number, String[] flags) {
    166     Assert.isWorkerThread();
    167     Assert.isNotNull(number);
    168 
    169     // Cequint is using custom arguments for content provider. See more details in b/35766080.
    170     try (Cursor cursor =
    171         context.getContentResolver().query(uri, EMPTY_PROJECTION, number, flags, null)) {
    172       if (cursor != null && cursor.moveToFirst()) {
    173         String city = getString(cursor, cursor.getColumnIndex(CITY_NAME));
    174         String state = getString(cursor, cursor.getColumnIndex(STATE_NAME));
    175         String stateAbbr = getString(cursor, cursor.getColumnIndex(STATE_ABBR));
    176         String country = getString(cursor, cursor.getColumnIndex(COUNTRY_NAME));
    177         String company = getString(cursor, cursor.getColumnIndex(COMPANY));
    178         String name = getString(cursor, cursor.getColumnIndex(NAME));
    179         String firstName = getString(cursor, cursor.getColumnIndex(FIRST_NAME));
    180         String lastName = getString(cursor, cursor.getColumnIndex(LAST_NAME));
    181         String imageUrl = getString(cursor, cursor.getColumnIndex(IMAGE));
    182         String displayName = getString(cursor, cursor.getColumnIndex(DISPLAY_NAME));
    183 
    184         String contactName =
    185             TextUtils.isEmpty(displayName)
    186                 ? generateDisplayName(firstName, lastName, company, name)
    187                 : displayName;
    188         String geoDescription = getGeoDescription(city, state, stateAbbr, country);
    189         LogUtil.d(
    190             "CequintCallerIdManager.lookup",
    191             "number: %s, contact name: %s, geo: %s, photo url: %s",
    192             LogUtil.sanitizePhoneNumber(number),
    193             LogUtil.sanitizePii(contactName),
    194             LogUtil.sanitizePii(geoDescription),
    195             imageUrl);
    196         return new CequintCallerIdContact(contactName, geoDescription, imageUrl);
    197       } else {
    198         LogUtil.d("CequintCallerIdManager.lookup", "No CequintCallerIdContact found");
    199         return null;
    200       }
    201     } catch (Exception e) {
    202       LogUtil.e("CequintCallerIdManager.lookup", "exception on query", e);
    203       return null;
    204     }
    205   }
    206 
    207   private static String getString(Cursor cursor, int columnIndex) {
    208     if (!cursor.isNull(columnIndex)) {
    209       String string = cursor.getString(columnIndex);
    210       if (!TextUtils.isEmpty(string)) {
    211         return string;
    212       }
    213     }
    214     return null;
    215   }
    216 
    217   /**
    218    * Returns generated name from other names, e.g. first name, last name etc. Returns null if there
    219    * is no other names.
    220    */
    221   @Nullable
    222   private static String generateDisplayName(
    223       String firstName, String lastName, String company, String name) {
    224     boolean hasFirstName = !TextUtils.isEmpty(firstName);
    225     boolean hasLastName = !TextUtils.isEmpty(lastName);
    226     boolean hasCompanyName = !TextUtils.isEmpty(company);
    227     boolean hasName = !TextUtils.isEmpty(name);
    228 
    229     StringBuilder stringBuilder = new StringBuilder();
    230 
    231     if (hasFirstName || hasLastName) {
    232       if (hasFirstName) {
    233         stringBuilder.append(firstName);
    234         if (hasLastName) {
    235           stringBuilder.append(" ");
    236         }
    237       }
    238       if (hasLastName) {
    239         stringBuilder.append(lastName);
    240       }
    241     } else if (hasCompanyName) {
    242       stringBuilder.append(company);
    243     } else if (hasName) {
    244       stringBuilder.append(name);
    245     } else {
    246       return null;
    247     }
    248 
    249     if (stringBuilder.length() > 0) {
    250       return stringBuilder.toString();
    251     }
    252     return null;
    253   }
    254 
    255   /** Returns geo location information. e.g. Mountain View, CA. */
    256   private static String getGeoDescription(
    257       String city, String state, String stateAbbr, String country) {
    258     String geoDescription = null;
    259 
    260     if (TextUtils.isEmpty(city) && !TextUtils.isEmpty(state)) {
    261       geoDescription = state;
    262     } else if (!TextUtils.isEmpty(city) && !TextUtils.isEmpty(stateAbbr)) {
    263       geoDescription = city + ", " + stateAbbr;
    264     } else if (!TextUtils.isEmpty(country)) {
    265       geoDescription = country;
    266     }
    267     return geoDescription;
    268   }
    269 
    270   private CequintCallerIdManager() {
    271     callLogCache = new ConcurrentHashMap<>();
    272   }
    273 }
    274