Add functionality for number verification

Add the NumberVerificationManager and plumb it into the incoming call
flow. Also make changes to testapps to enable testing this and add a
shell command to override the authorized package.

Bug: 119675160
Test: unit, testapps. GTS later.
Change-Id: I606c4595c7ff22074314b54cafe1eff475f15ea3
diff --git a/src/com/android/phone/NumberVerificationManager.java b/src/com/android/phone/NumberVerificationManager.java
new file mode 100644
index 0000000..9ec16f8
--- /dev/null
+++ b/src/com/android/phone/NumberVerificationManager.java
@@ -0,0 +1,192 @@
+/*
+ * 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().getVoiceRegState() == 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;
+    }
+}