Add Radio Interface Capabilities

* Introduces a set of capabilities specific for the radio interface
  that are determined by the vendor and are then surfaced through
  TelephonyManager.
* There are no capabilities yet defined and will come later in S
* Modified the phone boot up process to read these from the HAL first

Test: Added cts, ran cts, and std tests
Test: Tested that the proper log messages on the device both
      during boot up and cts (eg. the logs were just shown once)
Bug: 163085807
Merged-In: I5d213ccc7554a7be2c42f2b9cc23fb15577bab00
Change-Id: Ie75fab418c5db9d18af932ae7e4b82a3fad47c75
diff --git a/Android.bp b/Android.bp
index aff9f6a..9bdb15d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -84,6 +84,7 @@
         "android.hardware.radio.config-V1.0-java-shallow",
         "android.hardware.radio.config-V1.1-java-shallow",
         "android.hardware.radio.config-V1.2-java-shallow",
+        "android.hardware.radio.config-V1.3-java-shallow",
         "android.hardware.radio.deprecated-V1.0-java-shallow",
         "ecc-protos-lite",
         "libphonenumber-nogeocoder",
diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
index 03c6cbe..e68f8a9 100644
--- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
+++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
@@ -19,16 +19,19 @@
 import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
 import static android.telephony.TelephonyManager.EXTRA_ACTIVE_SIM_SUPPORTED_COUNT;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.content.Intent;
 import android.os.AsyncResult;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.RegistrantList;
 import android.os.storage.StorageManager;
 import android.sysprop.TelephonyProperties;
 import android.telephony.PhoneCapability;
+import android.telephony.RadioInterfaceCapabilities;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
@@ -56,6 +59,7 @@
     private static final int EVENT_GET_MODEM_STATUS = 101;
     private static final int EVENT_GET_MODEM_STATUS_DONE = 102;
     private static final int EVENT_GET_PHONE_CAPABILITY_DONE = 103;
+    private static final int EVENT_GET_HAL_DEVICE_CAPABILITIES_DONE = 104;
 
     private static PhoneConfigurationManager sInstance = null;
     private final Context mContext;
@@ -69,6 +73,8 @@
     private MockableInterface mMi = new MockableInterface();
     private TelephonyManager mTelephonyManager;
     private static final RegistrantList sMultiSimConfigChangeRegistrants = new RegistrantList();
+    private RadioInterfaceCapabilities mRadioInterfaceCapabilities;
+    private final Object mLockRadioInterfaceCapabilities = new Object();
 
     /**
      * Init method to instantiate the object
@@ -157,6 +163,7 @@
                                 + "No phone object provided for event " + msg.what);
                     }
                     getStaticPhoneCapability();
+                    getRadioInterfaceCapabilities();
                     break;
                 case EVENT_SWITCH_DSDS_CONFIG_DONE:
                     ar = (AsyncResult) msg.obj;
@@ -186,6 +193,10 @@
                     } else {
                         log(msg.what + " failure. Not getting phone capability." + ar.exception);
                     }
+                    break;
+                case EVENT_GET_HAL_DEVICE_CAPABILITIES_DONE:
+                    setupRadioInterfaceCapabilities((AsyncResult) msg.obj);
+                    break;
             }
         }
     }
@@ -304,6 +315,51 @@
     }
 
     /**
+     * Gets the radio interface capabilities for the device
+     */
+    @NonNull
+    public synchronized RadioInterfaceCapabilities getRadioInterfaceCapabilities() {
+        if (mRadioInterfaceCapabilities == null) {
+            //Only incur cost of synchronization block if mRadioInterfaceCapabilities isn't null
+            synchronized (mLockRadioInterfaceCapabilities) {
+                if (mRadioInterfaceCapabilities == null) {
+                    mRadioConfig.getHalDeviceCapabilities(
+                            mHandler.obtainMessage(EVENT_GET_HAL_DEVICE_CAPABILITIES_DONE));
+                    try {
+                        if (Looper.myLooper() == this.mHandler.getLooper()) {
+                            //Expected if this is called after the radio just turns on
+                            log("getRadioInterfaceCapabilities: myLoop == handler.getLooper "
+                                    + "returning non-available capabilities.");
+                        } else {
+                            mLockRadioInterfaceCapabilities.wait(2000);
+                        }
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+        }
+        if (mRadioInterfaceCapabilities == null) return new RadioInterfaceCapabilities();
+        else return mRadioInterfaceCapabilities;
+    }
+
+    private void setupRadioInterfaceCapabilities(@NonNull AsyncResult ar) {
+        if (mRadioInterfaceCapabilities == null) {
+            synchronized (mLockRadioInterfaceCapabilities) {
+                if (mRadioInterfaceCapabilities == null) {
+                    if (ar.exception != null) {
+                        loge("setupRadioInterfaceCapabilities: " + ar.exception);
+                    }
+                    log("setupRadioInterfaceCapabilities: "
+                            + "mRadioInterfaceCapabilities now setup");
+
+                    mRadioInterfaceCapabilities = (RadioInterfaceCapabilities) ar.result;
+                }
+                mLockRadioInterfaceCapabilities.notify();
+            }
+        }
+    }
+
+    /**
      * get configuration related status of each phone.
      */
     public PhoneCapability getCurrentPhoneCapability() {
@@ -497,4 +553,12 @@
     private static void log(String s) {
         Rlog.d(LOG_TAG, s);
     }
+
+    private static void loge(String s) {
+        Rlog.e(LOG_TAG, s);
+    }
+
+    private static void loge(String s, Exception ex) {
+        Rlog.e(LOG_TAG, s, ex);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/RadioConfig.java b/src/java/com/android/internal/telephony/RadioConfig.java
index 3ee4d48..ac49f56 100644
--- a/src/java/com/android/internal/telephony/RadioConfig.java
+++ b/src/java/com/android/internal/telephony/RadioConfig.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.telephony.RILConstants.RADIO_NOT_AVAILABLE;
 import static com.android.internal.telephony.RILConstants.REQUEST_NOT_SUPPORTED;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_PHONE_CAPABILITY;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SLOT_STATUS;
 import static com.android.internal.telephony.RILConstants
@@ -38,6 +39,7 @@
 import android.os.Registrant;
 import android.os.RemoteException;
 import android.os.WorkSource;
+import android.telephony.RadioInterfaceCapabilities;
 import android.util.SparseArray;
 
 import com.android.internal.telephony.uicc.IccSlotStatus;
@@ -64,6 +66,8 @@
 
     private static final HalVersion RADIO_CONFIG_HAL_VERSION_1_1 = new HalVersion(1, 1);
 
+    private static final HalVersion RADIO_CONFIG_HAL_VERSION_1_3 = new HalVersion(1, 3);
+
     private final boolean mIsMobileNetworkSupported;
     private volatile IRadioConfig mRadioConfigProxy = null;
     // IRadioConfig version
@@ -194,14 +198,26 @@
 
     private void updateRadioConfigProxy() {
         try {
+
             // Try to get service from different versions.
             try {
-                mRadioConfigProxy = android.hardware.radio.config.V1_1.IRadioConfig.getService(
+                mRadioConfigProxy = android.hardware.radio.config.V1_3.IRadioConfig.getService(
                         true);
-                mRadioConfigVersion = RADIO_CONFIG_HAL_VERSION_1_1;
+                mRadioConfigVersion = RADIO_CONFIG_HAL_VERSION_1_3;
             } catch (NoSuchElementException e) {
             }
 
+
+            if (mRadioConfigProxy == null) {
+                // Try to get service from different versions.
+                try {
+                    mRadioConfigProxy = android.hardware.radio.config.V1_1.IRadioConfig.getService(
+                            true);
+                    mRadioConfigVersion = RADIO_CONFIG_HAL_VERSION_1_1;
+                } catch (NoSuchElementException e) {
+                }
+            }
+
             if (mRadioConfigProxy == null) {
                 try {
                     mRadioConfigProxy = android.hardware.radio.config.V1_0
@@ -273,6 +289,31 @@
     }
 
     /**
+     * This is a helper function to be called when a RadioConfigResponse callback is called.
+     * It finds and returns RILRequest corresponding to the response if one is found.
+     * @param responseInfo RadioResponseInfo received in response callback
+     * @return RILRequest corresponding to the response
+     */
+    public RILRequest processResponse_1_6(
+            android.hardware.radio.V1_6.RadioResponseInfo responseInfo) {
+        int serial = responseInfo.serial;
+        int error = responseInfo.error;
+        int type = responseInfo.type;
+
+        if (type != RadioResponseType.SOLICITED) {
+            loge("processResponse: Unexpected response type " + type);
+        }
+
+        RILRequest rr = findAndRemoveRequestFromList(serial);
+        if (rr == null) {
+            loge("processResponse: Unexpected response! serial: " + serial + " error: " + error);
+            return null;
+        }
+
+        return rr;
+    }
+
+    /**
      * Wrapper function for IRadioConfig.getSimSlotsStatus().
      */
     public void getSimSlotsStatus(Message result) {
@@ -403,6 +444,8 @@
                 return "SET_PREFERRED_DATA_MODEM";
             case RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG:
                 return "SWITCH_DUAL_SIM_CONFIG";
+            case RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES:
+                return "GET_HAL_DEVICE_CAPABILITIES";
             default:
                 return "<unknown request " + request + ">";
         }
@@ -456,6 +499,46 @@
         }
     }
 
+    /**
+     * Gets the hal capabilities from the device.
+     */
+    public void getHalDeviceCapabilities(Message result) {
+        IRadioConfig radioConfigProxy = getRadioConfigProxy(result);
+        if (radioConfigProxy != null
+                && mRadioConfigVersion.greaterOrEqual(RADIO_CONFIG_HAL_VERSION_1_3)) {
+            android.hardware.radio.config.V1_3.IRadioConfig radioConfigProxy13 =
+                    (android.hardware.radio.config.V1_3.IRadioConfig) radioConfigProxy;
+            RILRequest rr = obtainRequest(RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES,
+                    result, mDefaultWorkSource);
+
+            if (DBG) {
+                logd(rr.serialString() + "> " + requestToString(rr.mRequest));
+            }
+
+            try {
+                mRadioConfigVersion = RADIO_CONFIG_HAL_VERSION_1_3;
+                radioConfigProxy13.getHalDeviceCapabilities(rr.mSerial);
+
+            } catch (RemoteException | RuntimeException e) {
+                resetProxyAndRequestList("getHalDeviceCapabilities", e);
+            }
+        } else {
+            if (result != null) {
+                if (DBG) {
+                    logd("RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES > REQUEST_NOT_SUPPORTED");
+                }
+                AsyncResult.forMessage(result, new RadioInterfaceCapabilities() /* send empty */,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            } else {
+                if (DBG) {
+                    logd("RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES > REQUEST_NOT_SUPPORTED "
+                            + "on complete message not set.");
+                }
+            }
+        }
+    }
+
     static ArrayList<IccSlotStatus> convertHalSlotStatus(
             ArrayList<android.hardware.radio.config.V1_0.SimSlotStatus> halSlotStatusList) {
         ArrayList<IccSlotStatus> response = new ArrayList<IccSlotStatus>(halSlotStatusList.size());
diff --git a/src/java/com/android/internal/telephony/RadioConfigResponse.java b/src/java/com/android/internal/telephony/RadioConfigResponse.java
index 8e509de..096cdf6 100644
--- a/src/java/com/android/internal/telephony/RadioConfigResponse.java
+++ b/src/java/com/android/internal/telephony/RadioConfigResponse.java
@@ -19,12 +19,14 @@
 import android.hardware.radio.V1_0.RadioError;
 import android.hardware.radio.V1_0.RadioResponseInfo;
 import android.hardware.radio.config.V1_1.ModemsConfig;
-import android.hardware.radio.config.V1_2.IRadioConfigResponse;
+import android.hardware.radio.config.V1_3.HalDeviceCapabilities;
+import android.hardware.radio.config.V1_3.IRadioConfigResponse;
 import android.telephony.ModemInfo;
 import android.telephony.PhoneCapability;
-import com.android.telephony.Rlog;
+import android.telephony.RadioInterfaceCapabilities;
 
 import com.android.internal.telephony.uicc.IccSlotStatus;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -226,4 +228,40 @@
             Rlog.e(TAG, "getModemsConfigResponse: Error " + responseInfo.toString());
         }
     }
+
+    /**
+     * Response function IRadioConfig.getHalDeviceCapabilities()
+     */
+    public void getHalDeviceCapabilitiesResponse(
+            android.hardware.radio.V1_6.RadioResponseInfo responseInfo,
+            HalDeviceCapabilities halDeviceCapabilities) {
+
+        //convert hal device capabilities to RadioInterfaceCapabilities
+
+        RILRequest rr = mRadioConfig.processResponse_1_6(responseInfo);
+        if (rr != null) {
+
+            RadioInterfaceCapabilities ret = new RadioInterfaceCapabilities();
+            /*
+             Code actual capabilities aren't ready yet, but it will look like this:
+             if (halDeviceCapability.cap1 == true) {
+                ret.addAvailableCapability(TelephonyManager.RADIO_INTERFACE_CAPABILITY_CAP1);
+             }
+             */
+
+            if (responseInfo.error == RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+                Rlog.d(TAG, rr.serialString() + "< "
+                        + mRadioConfig.requestToString(rr.mRequest));
+            } else {
+                rr.onError(responseInfo.error, ret);
+                Rlog.e(TAG, rr.serialString() + "< "
+                        + mRadioConfig.requestToString(rr.mRequest) + " error "
+                        + responseInfo.error);
+            }
+        } else {
+            Rlog.e(TAG, "getHalDeviceCapabilities: Error " + responseInfo.toString());
+        }
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/RadioInterfaceCapabilitiesTest.java b/tests/telephonytests/src/com/android/internal/telephony/RadioInterfaceCapabilitiesTest.java
new file mode 100644
index 0000000..ed52e37
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/RadioInterfaceCapabilitiesTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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;
+
+import static org.junit.Assert.assertTrue;
+
+import android.telephony.RadioInterfaceCapabilities;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RadioInterfaceCapabilitiesTest extends TelephonyTest {
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testAddCapability() {
+        RadioInterfaceCapabilities cap = new RadioInterfaceCapabilities();
+        cap.addSupportedCapability("TEST ME");
+        assertTrue(cap.isSupported("TEST ME"));
+    }
+}