Home | History | Annotate | Download | only in phonenumberproto
      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 
     17 package com.android.dialer.phonenumberproto;
     18 
     19 import android.support.annotation.NonNull;
     20 import android.support.annotation.Nullable;
     21 import android.support.annotation.WorkerThread;
     22 import android.telephony.PhoneNumberUtils;
     23 import android.text.TextUtils;
     24 import com.android.dialer.DialerPhoneNumber;
     25 import com.android.dialer.common.Assert;
     26 import com.android.dialer.common.LogUtil;
     27 import com.google.i18n.phonenumbers.NumberParseException;
     28 import com.google.i18n.phonenumbers.PhoneNumberUtil;
     29 import com.google.i18n.phonenumbers.PhoneNumberUtil.MatchType;
     30 import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
     31 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
     32 
     33 /**
     34  * Wrapper for selected methods in {@link PhoneNumberUtil} which uses the {@link DialerPhoneNumber}
     35  * lite proto instead of the {@link com.google.i18n.phonenumbers.Phonenumber.PhoneNumber} POJO.
     36  *
     37  * <p>All methods should be called on a worker thread.
     38  */
     39 public class DialerPhoneNumberUtil {
     40   private final PhoneNumberUtil phoneNumberUtil;
     41 
     42   @WorkerThread
     43   public DialerPhoneNumberUtil(@NonNull PhoneNumberUtil phoneNumberUtil) {
     44     Assert.isWorkerThread();
     45     this.phoneNumberUtil = Assert.isNotNull(phoneNumberUtil);
     46   }
     47 
     48   /**
     49    * Parses the provided raw phone number into a {@link DialerPhoneNumber}.
     50    *
     51    * @see PhoneNumberUtil#parse(CharSequence, String)
     52    */
     53   @WorkerThread
     54   public DialerPhoneNumber parse(@Nullable String numberToParse, @Nullable String defaultRegion) {
     55     Assert.isWorkerThread();
     56 
     57     DialerPhoneNumber.Builder dialerPhoneNumber = DialerPhoneNumber.newBuilder();
     58 
     59     if (defaultRegion != null) {
     60       dialerPhoneNumber.setCountryIso(defaultRegion);
     61     }
     62 
     63     // Numbers can be null or empty for incoming "unknown" calls.
     64     if (numberToParse == null) {
     65       return dialerPhoneNumber.build();
     66     }
     67 
     68     // If the number is a service number, just store the raw number and don't bother trying to parse
     69     // it. PhoneNumberUtil#parse ignores these characters which can lead to confusing behavior, such
     70     // as the numbers "#123" and "123" being considered the same. The "#" can appear in the middle
     71     // of a service number and the "*" can appear at the beginning (see a bug).
     72     if (isServiceNumber(numberToParse)) {
     73       return dialerPhoneNumber.setNormalizedNumber(numberToParse).build();
     74     }
     75 
     76     String postDialPortion = PhoneNumberUtils.extractPostDialPortion(numberToParse);
     77     if (!postDialPortion.isEmpty()) {
     78       dialerPhoneNumber.setPostDialPortion(postDialPortion);
     79     }
     80 
     81     String networkPortion = PhoneNumberUtils.extractNetworkPortion(numberToParse);
     82 
     83     try {
     84       PhoneNumber phoneNumber = phoneNumberUtil.parse(networkPortion, defaultRegion);
     85       if (phoneNumberUtil.isValidNumber(phoneNumber)) {
     86         String validNumber = phoneNumberUtil.format(phoneNumber, PhoneNumberFormat.E164);
     87         if (TextUtils.isEmpty(validNumber)) {
     88           throw new IllegalStateException(
     89               "e164 number should not be empty: " + LogUtil.sanitizePii(numberToParse));
     90         }
     91         // The E164 representation doesn't contain post-dial digits, but we need to preserve them.
     92         if (!postDialPortion.isEmpty()) {
     93           validNumber += postDialPortion;
     94         }
     95         return dialerPhoneNumber.setNormalizedNumber(validNumber).setIsValid(true).build();
     96       }
     97     } catch (NumberParseException e) {
     98       // fall through
     99     }
    100     return dialerPhoneNumber.setNormalizedNumber(networkPortion + postDialPortion).build();
    101   }
    102 
    103   /**
    104    * Returns true if the two numbers:
    105    *
    106    * <ul>
    107    *   <li>were parseable by libphonenumber (see {@link #parse(String, String)}),
    108    *   <li>are a {@link MatchType#SHORT_NSN_MATCH}, {@link MatchType#NSN_MATCH}, or {@link
    109    *       MatchType#EXACT_MATCH}, and
    110    *   <li>have the same post-dial digits.
    111    * </ul>
    112    *
    113    * <p>If either number is not parseable, returns true if their raw inputs have the same network
    114    * and post-dial portions.
    115    *
    116    * <p>An empty number is never considered to match another number.
    117    *
    118    * @see PhoneNumberUtil#isNumberMatch(PhoneNumber, PhoneNumber)
    119    */
    120   @WorkerThread
    121   public boolean isMatch(
    122       @NonNull DialerPhoneNumber firstNumberIn, @NonNull DialerPhoneNumber secondNumberIn) {
    123     Assert.isWorkerThread();
    124 
    125     // An empty number should not be combined with any other number.
    126     if (firstNumberIn.getNormalizedNumber().isEmpty()
    127         || secondNumberIn.getNormalizedNumber().isEmpty()) {
    128       return false;
    129     }
    130 
    131     // Two numbers with different countries should not match.
    132     if (!firstNumberIn.getCountryIso().equals(secondNumberIn.getCountryIso())) {
    133       return false;
    134     }
    135 
    136     PhoneNumber phoneNumber1 = null;
    137     try {
    138       phoneNumber1 =
    139           phoneNumberUtil.parse(firstNumberIn.getNormalizedNumber(), firstNumberIn.getCountryIso());
    140     } catch (NumberParseException e) {
    141       // fall through
    142     }
    143 
    144     PhoneNumber phoneNumber2 = null;
    145     try {
    146       phoneNumber2 =
    147           phoneNumberUtil.parse(
    148               secondNumberIn.getNormalizedNumber(), secondNumberIn.getCountryIso());
    149     } catch (NumberParseException e) {
    150       // fall through
    151     }
    152 
    153     // If either number is a service number or either number can't be parsed by libphonenumber, just
    154     // fallback to basic textual matching.
    155     if (isServiceNumber(firstNumberIn.getNormalizedNumber())
    156         || isServiceNumber(secondNumberIn.getNormalizedNumber())
    157         || phoneNumber1 == null
    158         || phoneNumber2 == null) {
    159       return firstNumberIn.getNormalizedNumber().equals(secondNumberIn.getNormalizedNumber());
    160     }
    161 
    162     // Both numbers are parseable, use more sophisticated libphonenumber matching.
    163     MatchType matchType = phoneNumberUtil.isNumberMatch(phoneNumber1, phoneNumber2);
    164 
    165     return (matchType == MatchType.SHORT_NSN_MATCH
    166             || matchType == MatchType.NSN_MATCH
    167             || matchType == MatchType.EXACT_MATCH)
    168         && firstNumberIn.getPostDialPortion().equals(secondNumberIn.getPostDialPortion());
    169   }
    170 
    171   private boolean isServiceNumber(@NonNull String rawNumber) {
    172     return rawNumber.contains("#") || rawNumber.startsWith("*");
    173   }
    174 }
    175