diff options
| -rw-r--r-- | Android.bp | 1 | ||||
| -rw-r--r-- | api/system-current.txt | 22 | ||||
| -rw-r--r-- | telephony/java/android/telephony/NumberVerificationCallback.java | 88 | ||||
| -rw-r--r-- | telephony/java/android/telephony/PhoneNumberRange.aidl | 19 | ||||
| -rw-r--r-- | telephony/java/android/telephony/PhoneNumberRange.java | 176 | ||||
| -rw-r--r-- | telephony/java/android/telephony/TelephonyManager.java | 54 | ||||
| -rw-r--r-- | telephony/java/com/android/internal/telephony/INumberVerificationCallback.aidl | 22 |
7 files changed, 382 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp index a59359c56a4c..cc9cfe95ff0a 100644 --- a/Android.bp +++ b/Android.bp @@ -572,6 +572,7 @@ java_defaults { "telephony/java/com/android/internal/telephony/IApnSourceService.aidl", "telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl", "telephony/java/com/android/internal/telephony/IMms.aidl", + "telephony/java/com/android/internal/telephony/INumberVerificationCallback.aidl", "telephony/java/com/android/internal/telephony/IOnSubscriptionsChangedListener.aidl", "telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl", "telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl", diff --git a/api/system-current.txt b/api/system-current.txt index 7a2c233d8e3b..6ee7afa51931 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5711,6 +5711,26 @@ package android.telephony { field public static final int RESULT_SUCCESS = 0; // 0x0 } + public abstract interface NumberVerificationCallback { + method public default void onCallReceived(java.lang.String); + method public default void onVerificationFailed(int); + field public static final int REASON_CONCURRENT_REQUESTS = 4; // 0x4 + field public static final int REASON_IN_ECBM = 5; // 0x5 + field public static final int REASON_IN_EMERGENCY_CALL = 6; // 0x6 + field public static final int REASON_NETWORK_NOT_AVAILABLE = 2; // 0x2 + field public static final int REASON_TIMED_OUT = 1; // 0x1 + field public static final int REASON_TOO_MANY_CALLS = 3; // 0x3 + field public static final int REASON_UNSPECIFIED = 0; // 0x0 + } + + public final class PhoneNumberRange implements android.os.Parcelable { + ctor public PhoneNumberRange(java.lang.String, java.lang.String, java.lang.String, java.lang.String); + method public int describeContents(); + method public boolean matches(java.lang.String); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.PhoneNumberRange> CREATOR; + } + public class PhoneStateListener { method public void onRadioPowerStateChanged(int); method public void onSrvccStateChanged(int); @@ -5875,6 +5895,7 @@ package android.telephony { method public boolean needsOtaServiceProvisioning(); method public boolean rebootRadio(); method public void requestCellInfoUpdate(android.os.WorkSource, java.util.concurrent.Executor, android.telephony.TelephonyManager.CellInfoCallback); + method public void requestNumberVerification(android.telephony.PhoneNumberRange, long, java.util.concurrent.Executor, android.telephony.NumberVerificationCallback); method public boolean resetRadioConfig(); method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>); method public void setCarrierDataEnabled(boolean); @@ -5904,6 +5925,7 @@ package android.telephony { field public static final java.lang.String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE"; field public static final java.lang.String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL"; field public static final java.lang.String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING"; + field public static final long MAX_NUMBER_VERIFICATION_TIMEOUT_MILLIS = 60000L; // 0xea60L field public static final int NETWORK_MODE_CDMA_EVDO = 4; // 0x4 field public static final int NETWORK_MODE_CDMA_NO_EVDO = 5; // 0x5 field public static final int NETWORK_MODE_EVDO_NO_CDMA = 6; // 0x6 diff --git a/telephony/java/android/telephony/NumberVerificationCallback.java b/telephony/java/android/telephony/NumberVerificationCallback.java new file mode 100644 index 000000000000..b00c57351589 --- /dev/null +++ b/telephony/java/android/telephony/NumberVerificationCallback.java @@ -0,0 +1,88 @@ +/* + * 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.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; + +/** + * A callback for number verification. After a request for number verification is received, + * the system will call {@link #onCallReceived(String)} if a phone call was received from a number + * matching the provided {@link PhoneNumberRange} or it will call {@link #onVerificationFailed(int)} + * if an error occurs. + * @hide + */ +@SystemApi +public interface NumberVerificationCallback { + /** @hide */ + @IntDef(value = {REASON_UNSPECIFIED, REASON_TIMED_OUT, REASON_NETWORK_NOT_AVAILABLE, + REASON_TOO_MANY_CALLS, REASON_CONCURRENT_REQUESTS, REASON_IN_ECBM, + REASON_IN_EMERGENCY_CALL}, + prefix = {"REASON_"}) + @interface NumberVerificationFailureReason {} + + /** + * Verification failed for an unspecified reason. + */ + int REASON_UNSPECIFIED = 0; + + /** + * Verification failed because no phone call was received from a matching number within the + * provided timeout. + */ + int REASON_TIMED_OUT = 1; + + /** + * Verification failed because no cellular voice network is available. + */ + int REASON_NETWORK_NOT_AVAILABLE = 2; + + /** + * Verification failed because there are currently too many ongoing phone calls for a new + * incoming phone call to be received. + */ + int REASON_TOO_MANY_CALLS = 3; + + /** + * Verification failed because a previous request for verification has not yet completed. + */ + int REASON_CONCURRENT_REQUESTS = 4; + + /** + * Verification failed because the phone is in emergency callback mode. + */ + int REASON_IN_ECBM = 5; + + /** + * Verification failed because the phone is currently in an emergency call. + */ + int REASON_IN_EMERGENCY_CALL = 6; + + /** + * Called when the device receives a phone call from the provided {@link PhoneNumberRange}. + * @param phoneNumber The phone number within the range that called. May or may not contain the + * country code, but will be entirely numeric. + */ + default void onCallReceived(@NonNull String phoneNumber) { } + + /** + * Called when verification fails for some reason. + * @param reason The reason for failure. + */ + default void onVerificationFailed(@NumberVerificationFailureReason int reason) { } +} diff --git a/telephony/java/android/telephony/PhoneNumberRange.aidl b/telephony/java/android/telephony/PhoneNumberRange.aidl new file mode 100644 index 000000000000..b0727be50e0b --- /dev/null +++ b/telephony/java/android/telephony/PhoneNumberRange.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 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; + +parcelable PhoneNumberRange; diff --git a/telephony/java/android/telephony/PhoneNumberRange.java b/telephony/java/android/telephony/PhoneNumberRange.java new file mode 100644 index 000000000000..d65156fd3ca2 --- /dev/null +++ b/telephony/java/android/telephony/PhoneNumberRange.java @@ -0,0 +1,176 @@ +/* + * 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 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(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; + } + } +} diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 278dab801260..45d914e0dfdd 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -77,6 +77,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telecom.ITelecomService; import com.android.internal.telephony.CellNetworkScanResult; import com.android.internal.telephony.IAns; +import com.android.internal.telephony.INumberVerificationCallback; import com.android.internal.telephony.IPhoneSubInfo; import com.android.internal.telephony.ITelephony; import com.android.internal.telephony.ITelephonyRegistry; @@ -1341,6 +1342,13 @@ public class TelephonyManager { */ public static final String EXTRA_RECOVERY_ACTION = "recoveryAction"; + /** + * The max value for the timeout passed in {@link #requestNumberVerification}. + * @hide + */ + @SystemApi + public static final long MAX_NUMBER_VERIFICATION_TIMEOUT_MILLIS = 60000; + // // // Device Info @@ -5503,6 +5511,52 @@ public class TelephonyManager { } /** + * Request that the next incoming call from a number matching {@code range} be intercepted. + * + * This API is intended for OEMs to provide a service for apps to verify the device's phone + * number. When called, the Telephony stack will store the provided {@link PhoneNumberRange} and + * intercept the next incoming call from a number that lies within the range, within a timeout + * specified by {@code timeoutMillis}. + * + * If such a phone call is received, the caller will be notified via + * {@link NumberVerificationCallback#onCallReceived(String)} on the provided {@link Executor}. + * If verification fails for any reason, the caller will be notified via + * {@link NumberVerificationCallback#onVerificationFailed(int)} + * on the provided {@link Executor}. + * + * In addition to the {@link Manifest.permission#MODIFY_PHONE_STATE} permission, callers of this + * API must also be listed in the device configuration as an authorized app in + * {@code packages/services/Telephony/res/values/config.xml} under the + * {@code config_number_verification_package_name} key. + * + * @hide + * @param range The range of phone numbers the caller expects a phone call from. + * @param timeoutMillis The amount of time to wait for such a call, or + * {@link #MAX_NUMBER_VERIFICATION_TIMEOUT_MILLIS}, whichever is lesser. + * @param executor The {@link Executor} that callbacks should be executed on. + * @param callback The callback to use for delivering results. + */ + @SystemApi + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void requestNumberVerification(@NonNull PhoneNumberRange range, long timeoutMillis, + @NonNull @CallbackExecutor Executor executor, + @NonNull NumberVerificationCallback callback) { + INumberVerificationCallback internalCallback = new INumberVerificationCallback.Stub() { + @Override + public void onCallReceived(String phoneNumber) throws RemoteException { + Binder.withCleanCallingIdentity(() -> callback.onCallReceived(phoneNumber)); + } + + @Override + public void onVerificationFailed(int reason) throws RemoteException { + Binder.withCleanCallingIdentity(() -> callback.onVerificationFailed(reason)); + } + }; + + // TODO -- call the aidl method + } + + /** * Sets a per-phone telephony property with the value specified. * * @hide diff --git a/telephony/java/com/android/internal/telephony/INumberVerificationCallback.aidl b/telephony/java/com/android/internal/telephony/INumberVerificationCallback.aidl new file mode 100644 index 000000000000..76918afb564a --- /dev/null +++ b/telephony/java/com/android/internal/telephony/INumberVerificationCallback.aidl @@ -0,0 +1,22 @@ +/* + * 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 com.android.internal.telephony; + +oneway interface INumberVerificationCallback { + void onCallReceived(String phoneNumber); + void onVerificationFailed(int reason); +} |