/* * 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.assisteddialing; import android.annotation.TargetApi; import android.content.Context; import android.os.Build.VERSION_CODES; import android.support.annotation.NonNull; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.util.ArraySet; import com.android.dialer.common.LogUtil; import com.google.i18n.phonenumbers.NumberParseException; import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource; import java.util.Arrays; import java.util.Locale; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; /** Ensures that a number is eligible for Assisted Dialing */ @TargetApi(VERSION_CODES.N) @SuppressWarnings("AndroidApiChecker") // Use of optional final class Constraints { private final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); private final Context context; /** * Create a new instance of Constraints. * * @param context The context used to determine whether or not a number is an emergency number. */ public Constraints(@NonNull Context context) { if (context == null) { throw new NullPointerException("Provided context cannot be null"); } this.context = context; } // TODO(erfanian): Ensure the below standard is consistent between libphonenumber and the // platform. // ISO 3166-1 alpha-2 Country Codes that are eligible for assisted dialing. private final String[] supportedCountryCodeValues = new String[] { "CA" /* Canada */, "GB" /* United Kingdom */, "JP" /* Japan */, "MX" /* Mexico */, "US" /* United States*/, }; private final Set supportedCountryCodes = Arrays.stream(supportedCountryCodeValues) .map(v -> v.toUpperCase(Locale.US)) .collect(Collectors.toCollection(ArraySet::new)); /** * Determines whether or not we think Assisted Dialing is possible given the provided parameters. * * @param numberToCheck A string containing the phone number. * @param userHomeCountryCode A string containing an ISO 3166-1 alpha-2 country code representing * the user's home country. * @param userRoamingCountryCode A string containing an ISO 3166-1 alpha-2 country code * representing the user's roaming country. * @return A boolean indicating whether or not the provided values are eligible for assisted * dialing. */ public boolean meetsPreconditions( @NonNull String numberToCheck, @NonNull String userHomeCountryCode, @NonNull String userRoamingCountryCode) { if (TextUtils.isEmpty(numberToCheck)) { LogUtil.i("Constraints.meetsPreconditions", "numberToCheck was empty"); return false; } if (TextUtils.isEmpty(userHomeCountryCode)) { LogUtil.i("Constraints.meetsPreconditions", "userHomeCountryCode was empty"); return false; } if (TextUtils.isEmpty(userRoamingCountryCode)) { LogUtil.i("Constraints.meetsPreconditions", "userRoamingCountryCode was empty"); return false; } userHomeCountryCode = userHomeCountryCode.toUpperCase(Locale.US); userRoamingCountryCode = userRoamingCountryCode.toUpperCase(Locale.US); Optional parsedPhoneNumber = parsePhoneNumber(numberToCheck, userHomeCountryCode); if (!parsedPhoneNumber.isPresent()) { LogUtil.i("Constraints.meetsPreconditions", "parsedPhoneNumber was empty"); return false; } return areSupportedCountryCodes(userHomeCountryCode, userRoamingCountryCode) && isUserRoaming(userHomeCountryCode, userRoamingCountryCode) && isNotInternationalNumber(parsedPhoneNumber) && isNotEmergencyNumber(numberToCheck, context) && isValidNumber(parsedPhoneNumber); } /** Returns a boolean indicating the value equivalence of the provided country codes. */ private boolean isUserRoaming( @NonNull String userHomeCountryCode, @NonNull String userRoamingCountryCode) { boolean result = !userHomeCountryCode.equals(userRoamingCountryCode); LogUtil.i("Constraints.isUserRoaming", String.valueOf(result)); return result; } /** * Returns a boolean indicating the support of both provided country codes for assisted dialing. * Both country codes must be allowed for the return value to be true. */ private boolean areSupportedCountryCodes( @NonNull String userHomeCountryCode, @NonNull String userRoamingCountryCode) { if (TextUtils.isEmpty(userHomeCountryCode)) { LogUtil.i("Constraints.areSupportedCountryCodes", "userHomeCountryCode was empty"); return false; } if (TextUtils.isEmpty(userRoamingCountryCode)) { LogUtil.i("Constraints.areSupportedCountryCodes", "userRoamingCountryCode was empty"); return false; } boolean result = supportedCountryCodes.contains(userHomeCountryCode) && supportedCountryCodes.contains(userRoamingCountryCode); LogUtil.i("Constraints.areSupportedCountryCodes", String.valueOf(result)); return result; } /** * A convenience method to take a number as a String and a specified country code, and return a * PhoneNumber object. */ private Optional parsePhoneNumber( @NonNull String numberToParse, @NonNull String userHomeCountryCode) { try { // TODO(erfanian): confirm behavior of blocking the foreground thread when moving to the // framework return Optional.of(phoneNumberUtil.parseAndKeepRawInput(numberToParse, userHomeCountryCode)); } catch (NumberParseException e) { LogUtil.i("Constraints.parsePhoneNumber", "could not parse the number"); return Optional.empty(); } } /** * Returns a boolean indicating if the provided number and home country code are already * internationally formatted. */ private boolean isNotInternationalNumber(@NonNull Optional parsedPhoneNumber) { if (parsedPhoneNumber.get().hasCountryCode() && parsedPhoneNumber.get().getCountryCodeSource() != CountryCodeSource.FROM_DEFAULT_COUNTRY) { LogUtil.i( "Constraints.isNotInternationalNumber", "phone number already provided the country code"); return false; } return true; } /** Returns a boolean indicating if the provided number is considered to be a valid number. */ private boolean isValidNumber(@NonNull Optional parsedPhoneNumber) { boolean result = PhoneNumberUtil.getInstance().isValidNumber(parsedPhoneNumber.get()); LogUtil.i("Constraints.isValidNumber", String.valueOf(result)); return result; } /** Returns a boolean indicating if the provided number is an emergency number. */ private boolean isNotEmergencyNumber(@NonNull String numberToCheck, @NonNull Context context) { // isEmergencyNumber may depend on network state, so also use isLocalEmergencyNumber when // roaming and out of service. boolean result = !PhoneNumberUtils.isEmergencyNumber(numberToCheck) && !PhoneNumberUtils.isLocalEmergencyNumber(context, numberToCheck); LogUtil.i("Constraints.isNotEmergencyNumber", String.valueOf(result)); return result; } }