Add API scaffolding for number verification

Add the PhoneNumberRange and NumberVerificationCallback classes. Add a
method in TelephonyManager to activate the API, but it does nothing for
now.

Bug: 119675160
Test: todo
Change-Id: I3ccd62b47f02a3aa324b675fdb16c8e7a1e9feec
This commit is contained in:
Hall Liu 2018-11-28 10:44:22 -08:00
parent bfd3237eac
commit 5314036bb1
7 changed files with 383 additions and 1 deletions

View File

@ -544,6 +544,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",

View File

@ -4466,8 +4466,8 @@ package android.service.carrier {
public abstract class ApnService extends android.app.Service {
ctor public ApnService();
method public abstract java.util.List<android.content.ContentValues> onRestoreApns(int);
method public android.os.IBinder onBind(android.content.Intent);
method public abstract java.util.List<android.content.ContentValues> onRestoreApns(int);
}
}
@ -5181,6 +5181,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);
@ -5345,6 +5365,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);
@ -5374,6 +5395,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

View File

@ -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) { }
}

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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;
@ -1332,6 +1333,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
@ -5476,6 +5484,52 @@ public class TelephonyManager {
return slotIndex;
}
/**
* 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.
*

View File

@ -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);
}