| /* |
| * 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; |
| } |
| } |