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