blob: 2298d407f874caacda50387d9c1e23e9ad646436 [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 com.android.phone;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.telephony.NumberVerificationCallback;
import android.telephony.PhoneNumberRange;
import android.telephony.ServiceState;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.INumberVerificationCallback;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
/**
* Singleton for managing the call based number verification requests.
*/
public class NumberVerificationManager {
interface PhoneListSupplier {
Phone[] getPhones();
}
private static NumberVerificationManager sInstance;
private static String sAuthorizedPackageOverride;
private PhoneNumberRange mCurrentRange;
private INumberVerificationCallback mCallback;
private final PhoneListSupplier mPhoneListSupplier;
// We don't really care what thread this runs on, since it's only used for a non-blocking
// timeout.
private Handler mHandler;
NumberVerificationManager(PhoneListSupplier phoneListSupplier) {
mPhoneListSupplier = phoneListSupplier;
mHandler = new Handler(Looper.getMainLooper());
}
private NumberVerificationManager() {
this(PhoneFactory::getPhones);
}
/**
* Check whether the incoming call matches one of the active filters. If so, call the callback
* that says that the number has been successfully verified.
* @param number A phone number
* @return true if the number matches, false otherwise
*/
public synchronized boolean checkIncomingCall(String number) {
if (mCurrentRange == null || mCallback == null) {
return false;
}
if (mCurrentRange.matches(number)) {
mCurrentRange = null;
try {
mCallback.onCallReceived(number);
return true;
} catch (RemoteException e) {
Log.w(NumberVerificationManager.class.getSimpleName(),
"Remote exception calling verification complete callback");
// Intercept the call even if there was a remote exception -- it's still going to be
// a strange call from a robot number
return true;
} finally {
mCallback = null;
}
}
return false;
}
synchronized void requestVerification(PhoneNumberRange numberRange,
INumberVerificationCallback callback, long timeoutMillis) {
if (!checkNumberVerificationFeasibility(callback)) {
return;
}
mCallback = callback;
mCurrentRange = numberRange;
mHandler.postDelayed(() -> {
synchronized (NumberVerificationManager.this) {
// Check whether the verification finished already -- if so, don't call anything.
if (mCallback != null && mCurrentRange != null) {
try {
mCallback.onVerificationFailed(NumberVerificationCallback.REASON_TIMED_OUT);
} catch (RemoteException e) {
Log.w(NumberVerificationManager.class.getSimpleName(),
"Remote exception calling verification error callback");
}
mCallback = null;
mCurrentRange = null;
}
}
}, timeoutMillis);
}
private boolean checkNumberVerificationFeasibility(INumberVerificationCallback callback) {
int reason = -1;
try {
if (mCurrentRange != null || mCallback != null) {
reason = NumberVerificationCallback.REASON_CONCURRENT_REQUESTS;
return false;
}
boolean doesAnyPhoneHaveRoomForIncomingCall = false;
boolean isAnyPhoneVoiceRegistered = false;
for (Phone phone : mPhoneListSupplier.getPhones()) {
// abort if any phone is in an emergency call or ecbm
if (phone.isInEmergencyCall()) {
reason = NumberVerificationCallback.REASON_IN_EMERGENCY_CALL;
return false;
}
if (phone.isInEcm()) {
reason = NumberVerificationCallback.REASON_IN_ECBM;
return false;
}
// make sure at least one phone is registered for voice
if (phone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE) {
isAnyPhoneVoiceRegistered = true;
}
// make sure at least one phone has room for an incoming call.
if (phone.getRingingCall().getState() == Call.State.IDLE
&& (phone.getForegroundCall().getState() == Call.State.IDLE
|| phone.getBackgroundCall().getState() == Call.State.IDLE)) {
doesAnyPhoneHaveRoomForIncomingCall = true;
}
}
if (!isAnyPhoneVoiceRegistered) {
reason = NumberVerificationCallback.REASON_NETWORK_NOT_AVAILABLE;
return false;
}
if (!doesAnyPhoneHaveRoomForIncomingCall) {
reason = NumberVerificationCallback.REASON_TOO_MANY_CALLS;
return false;
}
} finally {
if (reason >= 0) {
try {
callback.onVerificationFailed(reason);
} catch (RemoteException e) {
Log.w(NumberVerificationManager.class.getSimpleName(),
"Remote exception calling verification error callback");
}
}
}
return true;
}
/**
* Get the singleton instance of NumberVerificationManager.
* @return
*/
public static NumberVerificationManager getInstance() {
if (sInstance == null) {
sInstance = new NumberVerificationManager();
}
return sInstance;
}
static String getAuthorizedPackage(Context context) {
return !TextUtils.isEmpty(sAuthorizedPackageOverride) ? sAuthorizedPackageOverride :
context.getResources().getString(R.string.platform_number_verification_package);
}
/**
* Used by shell commands to override the authorized package name for number verification.
* @param pkgName
*/
static void overrideAuthorizedPackage(String pkgName) {
sAuthorizedPackageOverride = pkgName;
}
}