blob: c35a485d613f71fa22b026ebdc013dc69dd84faa [file] [log] [blame]
/*
* Copyright (C) 2018 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 android.telephony;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import java.util.Objects;
import java.util.regex.Pattern;
/**
* This class is used to represent a range of phone numbers. Each range corresponds to a contiguous
* block of phone numbers.
*
* Example:
* {@code
* {
* mCountryCode = "1"
* mPrefix = "650555"
* mLowerBound = "0055"
* mUpperBound = "0899"
* }
* }
* would match 16505550089 and 6505550472, but not 63827593759 or 16505550900
* @hide
*/
@SystemApi
public final class PhoneNumberRange implements Parcelable {
public static final @android.annotation.NonNull Creator<PhoneNumberRange> CREATOR = new Creator<PhoneNumberRange>() {
@Override
public PhoneNumberRange createFromParcel(Parcel in) {
return new PhoneNumberRange(in);
}
@Override
public PhoneNumberRange[] newArray(int size) {
return new PhoneNumberRange[size];
}
};
private final String mCountryCode;
private final String mPrefix;
private final String mLowerBound;
private final String mUpperBound;
/**
* @param countryCode The country code, omitting the leading "+"
* @param prefix A prefix that all numbers matching the range must have.
* @param lowerBound When concatenated with the prefix, represents the lower bound of phone
* numbers that match this range.
* @param upperBound When concatenated with the prefix, represents the upper bound of phone
* numbers that match this range.
*/
public PhoneNumberRange(@NonNull String countryCode, @NonNull String prefix,
@NonNull String lowerBound, @NonNull String upperBound) {
validateLowerAndUpperBounds(lowerBound, upperBound);
if (!Pattern.matches("[0-9]*", countryCode)) {
throw new IllegalArgumentException("Country code must be all numeric");
}
if (!Pattern.matches("[0-9]*", prefix)) {
throw new IllegalArgumentException("Prefix must be all numeric");
}
mCountryCode = countryCode;
mPrefix = prefix;
mLowerBound = lowerBound;
mUpperBound = upperBound;
}
private PhoneNumberRange(Parcel in) {
mCountryCode = in.readStringNoHelper();
mPrefix = in.readStringNoHelper();
mLowerBound = in.readStringNoHelper();
mUpperBound = in.readStringNoHelper();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStringNoHelper(mCountryCode);
dest.writeStringNoHelper(mPrefix);
dest.writeStringNoHelper(mLowerBound);
dest.writeStringNoHelper(mUpperBound);
}
@Override
public int describeContents() {
return 0;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PhoneNumberRange that = (PhoneNumberRange) o;
return Objects.equals(mCountryCode, that.mCountryCode)
&& Objects.equals(mPrefix, that.mPrefix)
&& Objects.equals(mLowerBound, that.mLowerBound)
&& Objects.equals(mUpperBound, that.mUpperBound);
}
@Override
public int hashCode() {
return Objects.hash(mCountryCode, mPrefix, mLowerBound, mUpperBound);
}
@Override
public String toString() {
return "PhoneNumberRange{"
+ "mCountryCode='" + mCountryCode + '\''
+ ", mPrefix='" + mPrefix + '\''
+ ", mLowerBound='" + mLowerBound + '\''
+ ", mUpperBound='" + mUpperBound + '\''
+ '}';
}
private void validateLowerAndUpperBounds(String lowerBound, String upperBound) {
if (lowerBound.length() != upperBound.length()) {
throw new IllegalArgumentException("Lower and upper bounds must have the same length");
}
if (!Pattern.matches("[0-9]*", lowerBound)) {
throw new IllegalArgumentException("Lower bound must be all numeric");
}
if (!Pattern.matches("[0-9]*", upperBound)) {
throw new IllegalArgumentException("Upper bound must be all numeric");
}
if (Integer.parseInt(lowerBound) > Integer.parseInt(upperBound)) {
throw new IllegalArgumentException("Lower bound must be lower than upper bound");
}
}
/**
* Checks to see if the provided phone number matches this range.
* @param number A phone number, with or without separators or a country code.
* @return {@code true} if the number matches, {@code false} otherwise.
*/
public boolean matches(@NonNull String number) {
// Check the prefix, make sure it matches either with or without the country code.
String normalizedNumber = number.replaceAll("[^0-9]", "");
String prefixWithCountryCode = mCountryCode + mPrefix;
String numberPostfix;
if (normalizedNumber.startsWith(prefixWithCountryCode)) {
numberPostfix = normalizedNumber.substring(prefixWithCountryCode.length());
} else if (normalizedNumber.startsWith(mPrefix)) {
numberPostfix = normalizedNumber.substring(mPrefix.length());
} else {
return false;
}
// Next check the postfix to make sure it lies within the bounds.
try {
int lower = Integer.parseInt(mLowerBound);
int upper = Integer.parseInt(mUpperBound);
int numberToCheck = Integer.parseInt(numberPostfix);
return numberToCheck <= upper && numberToCheck >= lower;
} catch (NumberFormatException e) {
Log.e(PhoneNumberRange.class.getSimpleName(), "Invalid bounds or number.", e);
return false;
}
}
}