Support Gba Api

Added api to support generic authentication architecture, and the test
application

Bug: 154865133
Test: atest cts/tests/tests/telephony/current/src/android/telephony/gba/cts/
Test: manual by GbaTestApp

Change-Id: Ib82637056e7daa451c644bb7a33ed308707b6b53
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c363811..02ff89f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -220,6 +220,7 @@
     <uses-permission android:name="android.permission.NETWORK_STATS_PROVIDER" />
     <uses-permission android:name="android.permission.MANAGE_SUBSCRIPTION_PLANS"/>
     <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"/>
+    <uses-permission android:name="android.permission.BIND_GBA_SERVICE"/>
 
     <!-- Needed to listen to changes in projection state. -->
     <uses-permission android:name="android.permission.READ_PROJECTION_STATE"/>
diff --git a/res/values/config.xml b/res/values/config.xml
index 5ade479..f69120f 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -296,4 +296,11 @@
         <item>250</item>
         <item>350</item>
     </integer-array>
+
+    <!-- String indicating the package name of the device GbaService implementation. -->
+    <string name="config_gba_package" translatable="false"/>
+    <!-- The interval to release/unbind GbaService after the authentication request
+        by millisecond. -1 - no release, 0 - release immediately,
+        positive n - release in n milliseconds -->
+    <integer name="config_gba_release_time">0</integer>
 </resources>
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 9082025..ca29534 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -77,6 +77,7 @@
 import android.telephony.CellInfoWcdma;
 import android.telephony.ClientRequestStats;
 import android.telephony.DataThrottlingRequest;
+import android.telephony.IBootstrapAuthenticationCallback;
 import android.telephony.ICellInfoCallback;
 import android.telephony.IccOpenLogicalChannelResponse;
 import android.telephony.LocationAccessPolicy;
@@ -103,6 +104,8 @@
 import android.telephony.VisualVoicemailSmsFilterSettings;
 import android.telephony.data.ApnSetting;
 import android.telephony.emergency.EmergencyNumber;
+import android.telephony.gba.GbaAuthRequest;
+import android.telephony.gba.UaSecurityProtocolIdentifier;
 import android.telephony.ims.ImsException;
 import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.RegistrationManager;
@@ -133,6 +136,7 @@
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.DefaultPhoneNotifier;
+import com.android.internal.telephony.GbaManager;
 import com.android.internal.telephony.HalVersion;
 import com.android.internal.telephony.IBooleanConsumer;
 import com.android.internal.telephony.ICallForwardingInfoCallback;
@@ -9171,6 +9175,44 @@
         }
     }
 
+    @Override
+    public void bootstrapAuthenticationRequest(int subId, int appType, Uri nafUrl,
+            UaSecurityProtocolIdentifier securityProtocol,
+            boolean forceBootStrapping, IBootstrapAuthenticationCallback callback)
+            throws RemoteException {
+        enforceModifyPermission();
+        if (DBG) {
+            log("bootstrapAuthenticationRequest, subId:" + subId + ", appType:"
+                    + appType + ", NAF:" + nafUrl + ", sp:" + securityProtocol
+                    + ", forceBootStrapping:" + forceBootStrapping + ", callback:" + callback);
+        }
+
+        if (!SubscriptionManager.isValidSubscriptionId(subId)
+                || appType < TelephonyManager.APPTYPE_UNKNOWN
+                || appType > TelephonyManager.APPTYPE_ISIM
+                || nafUrl == null || securityProtocol == null || callback == null) {
+            Log.d(LOG_TAG, "bootstrapAuthenticationRequest failed due to invalid parameters");
+            if (callback != null) {
+                try {
+                    callback.onAuthenticationFailure(
+                            0, TelephonyManager.GBA_FAILURE_REASON_FEATURE_NOT_SUPPORTED);
+                } catch (RemoteException exception) {
+                    log("Fail to notify onAuthenticationFailure due to " + exception);
+                }
+                return;
+            }
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            getGbaManager(subId).bootstrapAuthenticationRequest(
+                    new GbaAuthRequest(subId, appType, nafUrl, securityProtocol.toByteArray(),
+                    forceBootStrapping, callback));
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     /**
      * Attempts to set the radio power state for thermal reason. This does not guarantee that the
      * requested radio power state will actually be set. See {@link
@@ -9323,4 +9365,88 @@
 
         return thermalMitigationResult;
     }
+
+    /**
+     * Set the GbaService Package Name that Telephony will bind to.
+     *
+     * @param subId The sim that the GbaService is associated with.
+     * @param packageName The name of the package to be replaced with.
+     * @return true if setting the GbaService to bind to succeeded, false if it did not.
+     */
+    @Override
+    public boolean setBoundGbaServiceOverride(int subId, String packageName) {
+        enforceModifyPermission();
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return getGbaManager(subId).overrideServicePackage(packageName);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Return the package name of the currently bound GbaService.
+     *
+     * @param subId The sim that the GbaService is associated with.
+     * @return the package name of the GbaService configuration, null if GBA is not supported.
+     */
+    @Override
+    public String getBoundGbaService(int subId) {
+        enforceReadPrivilegedPermission("getBoundGbaServicePackage");
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return getGbaManager(subId).getServicePackage();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Set the release time for telephony to unbind GbaService.
+     *
+     * @param subId The sim that the GbaService is associated with.
+     * @param interval The release time to unbind GbaService by millisecond.
+     * @return true if setting the GbaService to bind to succeeded, false if it did not.
+     */
+    @Override
+    public boolean setGbaReleaseTimeOverride(int subId, int interval) {
+        enforceModifyPermission();
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return getGbaManager(subId).overrideReleaseTime(interval);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Return the release time for telephony to unbind GbaService.
+     *
+     * @param subId The sim that the GbaService is associated with.
+     * @return The release time to unbind GbaService by millisecond.
+     */
+    @Override
+    public int getGbaReleaseTime(int subId) {
+        enforceReadPrivilegedPermission("getGbaReleaseTime");
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return getGbaManager(subId).getReleaseTime();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private GbaManager getGbaManager(int subId) {
+        GbaManager instance = GbaManager.getInstance(subId);
+        if (instance == null) {
+            String packageName = mApp.getResources().getString(R.string.config_gba_package);
+            int releaseTime = mApp.getResources().getInteger(R.integer.config_gba_release_time);
+            instance = GbaManager.make(mApp, subId, packageName, releaseTime);
+        }
+        return instance;
+    }
 }
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 33d0721..8d67092 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -81,6 +81,12 @@
     private static final String CC_SET_VALUE = "set-value";
     private static final String CC_CLEAR_VALUES = "clear-values";
 
+    private static final String GBA_SUBCOMMAND = "gba";
+    private static final String GBA_SET_SERVICE = "set-service";
+    private static final String GBA_GET_SERVICE = "get-service";
+    private static final String GBA_SET_RELEASE_TIME = "set-release";
+    private static final String GBA_GET_RELEASE_TIME = "get-release";
+
     // Take advantage of existing methods that already contain permissions checks when possible.
     private final ITelephony mInterface;
 
@@ -161,6 +167,8 @@
                 return handleDataTestModeCommand();
             case END_BLOCK_SUPPRESSION:
                 return handleEndBlockSuppressionCommand();
+            case GBA_SUBCOMMAND:
+                return handleGbaCommand();
             default: {
                 return handleDefaultCommands(cmd);
             }
@@ -183,11 +191,14 @@
         pw.println("    Data Test Mode Commands.");
         pw.println("  cc");
         pw.println("    Carrier Config Commands.");
+        pw.println("  gba");
+        pw.println("    GBA Commands.");
         onHelpIms();
         onHelpEmergencyNumber();
         onHelpEndBlockSupperssion();
         onHelpDataTestMode();
         onHelpCc();
+        onHelpGba();
     }
 
     private void onHelpIms() {
@@ -293,6 +304,32 @@
         pw.println("          is specified, it will choose the default voice SIM slot.");
     }
 
+    private void onHelpGba() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Gba Commands:");
+        pw.println("  gba set-service [-s SLOT_ID] PACKAGE_NAME");
+        pw.println("    Sets the GbaService defined in PACKAGE_NAME to to be the bound.");
+        pw.println("    Options are:");
+        pw.println("      -s: The SIM slot ID to read carrier config value for. If no option");
+        pw.println("          is specified, it will choose the default voice SIM slot.");
+        pw.println("  gba get-service [-s SLOT_ID]");
+        pw.println("    Gets the package name of the currently defined GbaService.");
+        pw.println("    Options are:");
+        pw.println("      -s: The SIM slot ID to read carrier config value for. If no option");
+        pw.println("          is specified, it will choose the default voice SIM slot.");
+        pw.println("  gba set-release [-s SLOT_ID] n");
+        pw.println("    Sets the time to release/unbind GbaService in n milli-second.");
+        pw.println("    Do not release/unbind if n is -1.");
+        pw.println("    Options are:");
+        pw.println("      -s: The SIM slot ID to read carrier config value for. If no option");
+        pw.println("          is specified, it will choose the default voice SIM slot.");
+        pw.println("  gba get-release [-s SLOT_ID]");
+        pw.println("    Gets the time to release/unbind GbaService in n milli-sencond.");
+        pw.println("    Options are:");
+        pw.println("      -s: The SIM slot ID to read carrier config value for. If no option");
+        pw.println("          is specified, it will choose the default voice SIM slot.");
+    }
+
     private int handleImsCommand() {
         String arg = getNextArg();
         if (arg == null) {
@@ -1246,4 +1283,139 @@
         }
         return 0;
     }
+
+    private int handleGbaCommand() {
+        String arg = getNextArg();
+        if (arg == null) {
+            onHelpGba();
+            return 0;
+        }
+
+        switch (arg) {
+            case GBA_SET_SERVICE: {
+                return handleGbaSetServiceCommand();
+            }
+            case GBA_GET_SERVICE: {
+                return handleGbaGetServiceCommand();
+            }
+            case GBA_SET_RELEASE_TIME: {
+                return handleGbaSetReleaseCommand();
+            }
+            case GBA_GET_RELEASE_TIME: {
+                return handleGbaGetReleaseCommand();
+            }
+        }
+
+        return -1;
+    }
+
+    private int getSubId(String cmd) {
+        int slotId = getDefaultSlot();
+        String opt = getNextOption();
+        if (opt != null && opt.equals("-s")) {
+            try {
+                slotId = Integer.parseInt(getNextArgRequired());
+            } catch (NumberFormatException e) {
+                getErrPrintWriter().println(cmd + " requires an integer as a SLOT_ID.");
+                return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+            }
+        }
+        int[] subIds = SubscriptionManager.getSubId(slotId);
+        return subIds[0];
+    }
+
+    private int handleGbaSetServiceCommand() {
+        int subId = getSubId("gba set-service");
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return -1;
+        }
+
+        String packageName = getNextArg();
+        try {
+            if (packageName == null) {
+                packageName = "";
+            }
+            boolean result = mInterface.setBoundGbaServiceOverride(subId, packageName);
+            if (VDBG) {
+                Log.v(LOG_TAG, "gba set-service -s " + subId + " "
+                        + packageName + ", result=" + result);
+            }
+            getOutPrintWriter().println(result);
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "gba set-service " + subId + " "
+                    + packageName + ", error" + e.getMessage());
+            getErrPrintWriter().println("Exception: " + e.getMessage());
+            return -1;
+        }
+        return 0;
+    }
+
+    private int handleGbaGetServiceCommand() {
+        String result;
+
+        int subId = getSubId("gba get-service");
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return -1;
+        }
+
+        try {
+            result = mInterface.getBoundGbaService(subId);
+        } catch (RemoteException e) {
+            return -1;
+        }
+        if (VDBG) {
+            Log.v(LOG_TAG, "gba get-service -s " + subId + ", returned: " + result);
+        }
+        getOutPrintWriter().println(result);
+        return 0;
+    }
+
+    private int handleGbaSetReleaseCommand() {
+        //the release time value could be -1
+        int subId = getRemainingArgsCount() > 1 ? getSubId("gba set-release")
+                : SubscriptionManager.getDefaultSubscriptionId();
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return -1;
+        }
+
+        String intervalStr = getNextArg();
+        if (intervalStr == null) {
+            return -1;
+        }
+
+        try {
+            int interval = Integer.parseInt(intervalStr);
+            boolean result = mInterface.setGbaReleaseTimeOverride(subId, interval);
+            if (VDBG) {
+                Log.v(LOG_TAG, "gba set-release -s " + subId + " "
+                        + intervalStr + ", result=" + result);
+            }
+            getOutPrintWriter().println(result);
+        } catch (NumberFormatException | RemoteException e) {
+            Log.w(LOG_TAG, "gba set-release -s " + subId + " "
+                    + intervalStr + ", error" + e.getMessage());
+            getErrPrintWriter().println("Exception: " + e.getMessage());
+            return -1;
+        }
+        return 0;
+    }
+
+    private int handleGbaGetReleaseCommand() {
+        int subId = getSubId("gba get-release");
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return -1;
+        }
+
+        int result = 0;
+        try {
+            result = mInterface.getGbaReleaseTime(subId);
+        } catch (RemoteException e) {
+            return -1;
+        }
+        if (VDBG) {
+            Log.v(LOG_TAG, "gba get-release -s " + subId + ", returned: " + result);
+        }
+        getOutPrintWriter().println(result);
+        return 0;
+    }
 }
diff --git a/testapps/GbaTestApp/Android.bp b/testapps/GbaTestApp/Android.bp
new file mode 100644
index 0000000..cb6df4e
--- /dev/null
+++ b/testapps/GbaTestApp/Android.bp
@@ -0,0 +1,26 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// 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.
+
+android_test {
+    name: "GbaTestApp",
+    static_libs: [
+        "androidx.appcompat_appcompat",
+	"androidx-constraintlayout_constraintlayout",
+	"ub-uiautomator",
+    ],
+    srcs: ["src/**/*.java"],
+    javacflags: ["-parameters"],
+    platform_apis: true,
+    certificate: "platform",
+}
diff --git a/testapps/GbaTestApp/AndroidManifest.xml b/testapps/GbaTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..8554461
--- /dev/null
+++ b/testapps/GbaTestApp/AndroidManifest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.phone.testapps.gbatestapp">
+
+    <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.BIND_GBA_SERVICE" />
+
+    <application
+        android:allowBackup="true"
+        android:label="@string/app_name"
+        android:theme="@style/Theme.AppCompat"
+        android:supportsRtl="true">
+        <service
+            android:name=".TestGbaService"
+            android:directBootAware="true"
+            android:permission="android.permission.BIND_GBA_SERVICE"
+            android:enabled="true"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.telephony.gba.GbaService"/>
+            </intent-filter>
+        </service>
+
+        <activity android:name=".MainActivity"
+            android:enabled="true"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/testapps/GbaTestApp/res/layout/fragment_carrier_config.xml b/testapps/GbaTestApp/res/layout/fragment_carrier_config.xml
new file mode 100644
index 0000000..f15fa2a
--- /dev/null
+++ b/testapps/GbaTestApp/res/layout/fragment_carrier_config.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/black"
+    tools:context=".ui.main.CarrierConfigFragment">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/service_package_name" />
+
+        <EditText
+            android:id="@+id/editServicePackageName"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="0"
+            android:ems="10"
+            android:gravity="start|top"
+            android:inputType="textMultiLine" />
+
+        <TextView
+            android:id="@+id/textTestLabel"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/service_release_time" />
+
+        <EditText
+            android:id="@+id/editServiceReleaseTime"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ems="10"
+            android:inputType="numberSigned" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/layout_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom"
+        android:orientation="vertical">
+
+        <Button
+            android:id="@+id/carrier_config_clear"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/button_name_clear" />
+
+        <Button
+            android:id="@+id/carrier_config_done"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/button_name_done" />
+    </LinearLayout>
+
+</FrameLayout>
diff --git a/testapps/GbaTestApp/res/layout/fragment_service_config.xml b/testapps/GbaTestApp/res/layout/fragment_service_config.xml
new file mode 100644
index 0000000..50090c2
--- /dev/null
+++ b/testapps/GbaTestApp/res/layout/fragment_service_config.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/black"
+    tools:context=".ui.main.ServiceConfigFragment">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_above="@id/layout_buttons"
+        android:orientation="vertical">
+
+        <CheckBox
+            android:id="@+id/checkBoxResult"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/response_success" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/response_key" />
+
+        <EditText
+            android:id="@+id/editKey"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ems="10"
+            android:inputType="textPersonName" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:editable="false"
+            android:text="@string/response_btid" />
+
+        <EditText
+            android:id="@+id/editBTid"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ems="10"
+            android:inputType="textPersonName" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/response_fail_reason" />
+
+        <EditText
+            android:id="@+id/editFailReason"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ems="10"
+            android:inputType="numberSigned" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/layout_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_gravity="bottom"
+        android:orientation="vertical">
+
+        <Button
+            android:id="@+id/service_config_clear"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/button_name_clear" />
+
+        <Button
+            android:id="@+id/service_config_done"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/button_name_done" />
+    </LinearLayout>
+
+</FrameLayout>
diff --git a/testapps/GbaTestApp/res/layout/fragment_test_config.xml b/testapps/GbaTestApp/res/layout/fragment_test_config.xml
new file mode 100644
index 0000000..d8016f0
--- /dev/null
+++ b/testapps/GbaTestApp/res/layout/fragment_test_config.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/black"
+    tools:context=".ui.main.TestConfigFragment">
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_marginBottom="8dp"
+        android:scrollbarStyle="outsideInset"
+        app:layout_constraintBottom_toTopOf="@id/layout_buttons"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/request_app_type" />
+
+            <EditText
+                android:id="@+id/editAppType"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ems="10"
+                android:inputType="numberSigned" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/request_naf_url" />
+
+            <EditText
+                android:id="@+id/editUrl"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ems="10"
+                android:inputType="textUri" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/request_org" />
+
+            <EditText
+                android:id="@+id/editOrg"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ems="10"
+                android:inputType="numberSigned" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/request_security_protocol" />
+
+            <EditText
+                android:id="@+id/editSpId"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ems="10"
+                android:inputType="numberSigned" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/request_tls_cipher_suite" />
+
+            <EditText
+                android:id="@+id/editTlsCs"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ems="10"
+                android:inputType="numberSigned" />
+
+            <CheckBox
+                android:id="@+id/checkBoxForce"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/request_force_bootstrapping" />
+
+        </LinearLayout>
+    </ScrollView>
+
+    <LinearLayout
+        android:id="@+id/layout_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="false"
+        android:layout_gravity="bottom"
+        android:orientation="vertical"
+        app:layout_constraintBottom_toBottomOf="parent">
+
+        <Button
+            android:id="@+id/client_config_clear"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/button_name_clear" />
+
+        <Button
+            android:id="@+id/client_config_done"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/button_name_done" />
+    </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/testapps/GbaTestApp/res/layout/main_activity.xml b/testapps/GbaTestApp/res/layout/main_activity.xml
new file mode 100644
index 0000000..1dfb73b
--- /dev/null
+++ b/testapps/GbaTestApp/res/layout/main_activity.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity" />
\ No newline at end of file
diff --git a/testapps/GbaTestApp/res/layout/main_fragment.xml b/testapps/GbaTestApp/res/layout/main_fragment.xml
new file mode 100644
index 0000000..33bb6e1
--- /dev/null
+++ b/testapps/GbaTestApp/res/layout/main_fragment.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/main"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".ui.main.MainFragment">
+
+    <LinearLayout
+        android:id="@+id/layout_config"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:orientation="vertical"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/label_settings" />
+
+        <Button
+            android:id="@+id/carrier_config_change_button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/label_carrier" />
+
+        <Button
+            android:id="@+id/service_config"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/label_service" />
+
+        <Button
+            android:id="@+id/client_config"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/label_test" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/layout_test"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:orientation="vertical"
+        app:layout_constraintBottom_toTopOf="@id/layout_exit"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/layout_config">
+
+        <Button
+            android:id="@+id/send_request"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/button_name_running" />
+
+        <TextView
+            android:id="@+id/textTestLabel"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/label_test_result" />
+        <ScrollView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical" >
+
+                <TextView
+                    android:id="@+id/viewTestOutput"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"/>
+            </LinearLayout>
+        </ScrollView>
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/layout_exit"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_gravity="bottom"
+        android:orientation="vertical"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent">
+
+        <Button
+            android:id="@+id/test_exit"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/button_name_exit" />
+    </LinearLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/testapps/GbaTestApp/res/values/colors.xml b/testapps/GbaTestApp/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/testapps/GbaTestApp/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>
\ No newline at end of file
diff --git a/testapps/GbaTestApp/res/values/strings.xml b/testapps/GbaTestApp/res/values/strings.xml
new file mode 100644
index 0000000..e74c181
--- /dev/null
+++ b/testapps/GbaTestApp/res/values/strings.xml
@@ -0,0 +1,30 @@
+<resources>
+    <string name="app_name">GbaTestApp</string>
+    <string name="label_settings">Settings</string>
+    <string name="label_carrier">Carrier Config</string>
+    <string name="label_service">Service Config</string>
+    <string name="label_test">Test Config</string>
+    <string name="button_name_running">Running</string>
+    <string name="button_name_exit">Exit</string>
+    <string name="label_test_result">Test Result</string>
+    <string name="button_name_clear">Reset</string>
+    <string name="button_name_done">Done</string>
+    <string name="title_activity_carrier_config">CarrierConfigActivity</string>
+    <string name="title_activity_service_config">ServiceConfigActivity</string>
+    <string name="title_activity_test_config">TestConfigActivity</string>
+    <string name="service_package_name">Package name of GBA service</string>
+    <string name="service_release_time">How long to release service after calling</string>
+    <string name="request_app_type">UICC App Type</string>
+    <string name="request_naf_url">Network application function (NAF) URL</string>
+    <string name="request_force_bootstrapping">Force Bootstrapping?</string>
+    <string name="request_org">Organization Code</string>
+    <string name="request_security_protocol">UA Security Protocol ID</string>
+    <string name="request_tls_cipher_suite">TLS Cipher Suite ID</string>
+    <string name="response_success">GBA Auth Success?</string>
+    <string name="response_fail_reason">Fail Reason ID</string>
+    <string name="response_key">GBA Key (CK + IK)</string>
+    <string name="response_btid">Bootstrapping Transaction Identifier (B-TID)</string>
+    <string name="sample_naf">3GPP-bootstrapping@naf1.operator.com</string>
+    <string name="sample_btid">(B-TID)</string>
+    <string name="sample_key">6629fae49393a05397450978507c4ef1</string>
+</resources>
diff --git a/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/MainActivity.java b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/MainActivity.java
new file mode 100644
index 0000000..72cbf5c
--- /dev/null
+++ b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/MainActivity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 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.phone.testapps.gbatestapp;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.android.phone.testapps.gbatestapp.ui.main.MainFragment;
+
+/** main activity of the gba test app */
+public class MainActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main_activity);
+        if (savedInstanceState == null) {
+            getSupportFragmentManager().beginTransaction()
+                    .replace(R.id.container, MainFragment.newInstance())
+                    .commitNow();
+        }
+    }
+}
diff --git a/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/Settings.java b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/Settings.java
new file mode 100644
index 0000000..eaa424a
--- /dev/null
+++ b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/Settings.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright 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.phone.testapps.gbatestapp;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.RemoteException;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.ITelephony;
+
+import java.util.Locale;
+
+/** class to load and save the settings */
+public class Settings {
+
+    private static final String TAG = "SETTINGS";
+
+    private static final String PREF_CARRIER_CONFIG = "pref_carrier_config";
+    private static final String KEY_SERVICE_PACKAGE = "key_service_package";
+    private static final String KEY_RELEASE_TIME = "key_release_time";
+
+    private static final String PREF_TEST_CONFIG = "pref_test_config";
+    private static final String KEY_APP_TYPE = "key_app_type";
+    private static final String KEY_NAF_URL = "key_naf_url";
+    private static final String KEY_FORCE_BT = "key_force_bt";
+    private static final String KEY_ORG = "org";
+    private static final String KEY_SP_ID = "key_sp_id";
+    private static final String KEY_TLS_CS = "key_tls_cs";
+
+    private static final String PREF_SERVICE_CONFIG = "pref_carrier_config";
+    private static final String KEY_AUTH_RESULT = "key_auth_result";
+    private static final String KEY_GBA_KEY = "key_gba_key";
+    private static final String KEY_B_TID = "key_b_tid";
+    private static final String KEY_FAIL_REASON = "key_fail_reason";
+
+    private ITelephony mTelephony;
+    private int mSubId;
+    private String mServicePackageName;
+    private int mReleaseTime;
+    private int mAppType;
+    private String mNafUrl;
+    private boolean mForceBootstrap;
+    private int mOrg;
+    private int mSpId;
+    private int mTlsCs;
+    private boolean mIsAuthSuccess;
+    private String mGbaKey;
+    private String mBTid;
+    private int mFailReason;
+
+    private static Settings sInstance;
+
+    private Settings(Context cxt) {
+        mTelephony = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
+                .getTelephonyServiceManager().getTelephonyServiceRegisterer().get());
+        mSubId = SubscriptionManager.getDefaultSubscriptionId();
+        SharedPreferences sharedPref = cxt.getSharedPreferences(
+                PREF_CARRIER_CONFIG, Context.MODE_PRIVATE);
+        mServicePackageName = loadServicePackageName(mSubId, sharedPref);
+        mReleaseTime = loadReleaseTime(mSubId, sharedPref);
+
+        sharedPref = cxt.getSharedPreferences(PREF_TEST_CONFIG, Context.MODE_PRIVATE);
+        mAppType = sharedPref.getInt(KEY_APP_TYPE, TelephonyManager.APPTYPE_SIM);
+        mNafUrl = sharedPref.getString(KEY_NAF_URL, null);
+        mForceBootstrap = sharedPref.getBoolean(KEY_FORCE_BT, false);
+        mOrg = sharedPref.getInt(KEY_ORG, 0);
+        mSpId = sharedPref.getInt(KEY_SP_ID, 0);
+        mTlsCs = sharedPref.getInt(KEY_TLS_CS, 0);
+
+        sharedPref = cxt.getSharedPreferences(PREF_SERVICE_CONFIG, Context.MODE_PRIVATE);
+        mIsAuthSuccess = sharedPref.getBoolean(KEY_AUTH_RESULT, false);
+        mFailReason = sharedPref.getInt(KEY_FAIL_REASON, 0);
+        mGbaKey = sharedPref.getString(KEY_GBA_KEY, null);
+        mBTid = sharedPref.getString(KEY_B_TID, null);
+    }
+
+    /** Get the instance of Settings*/
+    public static Settings getSettings(Context cxt) {
+        if (sInstance == null) {
+            sInstance = new Settings(cxt);
+        }
+
+        return sInstance;
+    }
+
+    /** update carrier config settings */
+    public void updateCarrierConfig(Context cxt, String packageName, int releaseTime) {
+        new Thread(() -> {
+            synchronized (PREF_CARRIER_CONFIG) {
+
+                if (TextUtils.equals(mServicePackageName, packageName)
+                        && (mReleaseTime == releaseTime)) {
+                    return;
+                }
+
+                if (!TextUtils.equals(mServicePackageName, packageName)) {
+                    mServicePackageName = packageName;
+
+                    try {
+                        mTelephony.setBoundGbaServiceOverride(mSubId, packageName);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "fail to set package name due to " + e);
+                    }
+
+                }
+
+                if (mReleaseTime != releaseTime) {
+                    mReleaseTime = releaseTime;
+
+                    try {
+                        mTelephony.setGbaReleaseTimeOverride(mSubId, releaseTime);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "fail to set release time due to " + e);
+                    }
+                }
+
+                SharedPreferences sharedPref = cxt.getSharedPreferences(
+                        PREF_CARRIER_CONFIG, Context.MODE_PRIVATE);
+                SharedPreferences.Editor editor = sharedPref.edit();
+                editor.putString(KEY_SERVICE_PACKAGE, packageName);
+                editor.putInt(KEY_RELEASE_TIME, releaseTime);
+                editor.commit();
+            }
+        }).start();
+    }
+
+    /** get the config of gba service package name */
+    public String getServicePackageName() {
+        synchronized (PREF_CARRIER_CONFIG) {
+            return mServicePackageName;
+        }
+    }
+
+    /** get the config of gba release time */
+    public int getReleaseTime() {
+        synchronized (PREF_CARRIER_CONFIG) {
+            return mReleaseTime;
+        }
+    }
+
+    /** get the config of gba service package name used for now*/
+    public String loadServicePackageName(int subId, SharedPreferences sharedPref) {
+        try {
+            return mTelephony.getBoundGbaService(subId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "fail to get package name due to " + e);
+        }
+        return sharedPref != null ? sharedPref.getString(KEY_SERVICE_PACKAGE, null) : null;
+    }
+
+    /** get the config of gba release time used for now */
+    public int loadReleaseTime(int subId, SharedPreferences sharedPref) {
+        try {
+            return mTelephony.getGbaReleaseTime(subId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "fail to get package name due to " + e);
+        }
+        return sharedPref != null ? sharedPref.getInt(KEY_RELEASE_TIME, 0) : 0;
+    }
+
+    /** update the config of test gba service */
+    public void updateServiceConfig(Context cxt, boolean success, int reason,
+                String key, String btId) {
+        new Thread(() -> {
+            synchronized (PREF_SERVICE_CONFIG) {
+                mIsAuthSuccess = success;
+                mFailReason = reason;
+                mGbaKey = key;
+                mBTid = btId;
+                SharedPreferences sharedPref = cxt.getSharedPreferences(
+                        PREF_SERVICE_CONFIG, Context.MODE_PRIVATE);
+                SharedPreferences.Editor editor = sharedPref.edit();
+                editor.putBoolean(KEY_AUTH_RESULT, success);
+                editor.putInt(KEY_FAIL_REASON, reason);
+                editor.putString(KEY_GBA_KEY, key);
+                editor.putString(KEY_B_TID, btId);
+                editor.commit();
+            }
+        }).start();
+    }
+
+    /** get the config of the authentication result */
+    public boolean getAuthResult() {
+        synchronized (PREF_SERVICE_CONFIG) {
+            return mIsAuthSuccess;
+        }
+    }
+
+    /** get the config of authentication fail cause */
+    public int getFailReason() {
+        synchronized (PREF_SERVICE_CONFIG) {
+            return mFailReason;
+        }
+    }
+
+    /** get the config of GBA key */
+    public String getGbaKey() {
+        synchronized (PREF_SERVICE_CONFIG) {
+            return mGbaKey;
+        }
+    }
+
+    /** get the config of B-Tid */
+    public String getBTid() {
+        synchronized (PREF_SERVICE_CONFIG) {
+            return mBTid;
+        }
+    }
+
+    /** update the config of the test */
+    public void updateTestConfig(Context cxt, int appType, String url,
+            boolean force, int org, int spId, int tlsCs) {
+        new Thread(() -> {
+            synchronized (PREF_TEST_CONFIG) {
+                mAppType = appType;
+                mNafUrl = url;
+                mForceBootstrap = force;
+                mOrg = org;
+                mSpId = spId;
+                mTlsCs = tlsCs;
+
+                SharedPreferences sharedPref = cxt.getSharedPreferences(
+                        PREF_TEST_CONFIG, Context.MODE_PRIVATE);
+                SharedPreferences.Editor editor = sharedPref.edit();
+                editor.putInt(KEY_APP_TYPE, appType);
+                editor.putString(KEY_NAF_URL, url);
+                editor.putBoolean(KEY_FORCE_BT, force);
+                editor.putInt(KEY_ORG, org);
+                editor.putInt(KEY_SP_ID, spId);
+                editor.putInt(KEY_TLS_CS, tlsCs);
+                editor.commit();
+            }
+        }).start();
+    }
+
+    /** get the config of the uicc application type*/
+    public int getAppType() {
+        synchronized (PREF_TEST_CONFIG) {
+            return mAppType;
+        }
+    }
+
+    /** get the config of NAF url */
+    public String getNafUrl() {
+        synchronized (PREF_TEST_CONFIG) {
+            return mNafUrl;
+        }
+    }
+
+    /** get the config if bootstrap is forced */
+    public boolean isForceBootstrap() {
+        synchronized (PREF_TEST_CONFIG) {
+            return mForceBootstrap;
+        }
+    }
+
+    /** get the config of the organization code */
+    public int getOrg() {
+        synchronized (PREF_TEST_CONFIG) {
+            return mOrg;
+        }
+    }
+
+    /** get the config of the security protocol id */
+    public int getSpId() {
+        synchronized (PREF_TEST_CONFIG) {
+            return mSpId;
+        }
+    }
+
+    /** get the config of the tls ciper suite id */
+    public int getTlsCs() {
+        synchronized (PREF_TEST_CONFIG) {
+            return mTlsCs;
+        }
+    }
+
+    /** convert byte arry to hex string */
+    public static String byteArrayToHexString(byte[] data) {
+        if (data == null || data.length == 0) {
+            return "";
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (byte b : data) {
+            sb.append(String.format(Locale.US, "%02X", b));
+        }
+        return sb.toString();
+    }
+}
diff --git a/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/TestGbaService.java b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/TestGbaService.java
new file mode 100644
index 0000000..4b0636c
--- /dev/null
+++ b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/TestGbaService.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 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.phone.testapps.gbatestapp;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.IBinder;
+import android.telephony.gba.GbaService;
+import android.util.Log;
+
+/** test GbaService to be used for Gba api test */
+public class TestGbaService extends GbaService {
+
+    private static final String TAG = "TestGbaService";
+
+    private Settings mSettings;
+
+    @Override
+    public void onCreate() {
+        Log.i(TAG, "TestGbaService: onCreate");
+        mSettings = Settings.getSettings(getApplicationContext());
+    }
+
+    @Override
+    public void onAuthenticationRequest(int subId, int token, int appType,
+            @NonNull Uri nafUrl, @NonNull byte[] securityProtocol, boolean forceBootStrapping) {
+        boolean isSuccess = mSettings.getAuthResult();
+        int reason = mSettings.getFailReason();
+        String key = mSettings.getGbaKey();
+        String btid = mSettings.getBTid();
+
+        if (isSuccess) {
+            reportKeysAvailable(token, key.getBytes(), btid);
+        } else {
+            reportAuthenticationFailure(token, reason);
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        Log.d(TAG, "onBind intent:" + intent);
+        return super.onBind(intent);
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.d(TAG, "onDestroy!");
+        super.onDestroy();
+    }
+}
diff --git a/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/CarrierConfigFragment.java b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/CarrierConfigFragment.java
new file mode 100644
index 0000000..b0bfc32
--- /dev/null
+++ b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/CarrierConfigFragment.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 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.phone.testapps.gbatestapp.ui.main;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.phone.testapps.gbatestapp.R;
+import com.android.phone.testapps.gbatestapp.Settings;
+import com.android.phone.testapps.gbatestapp.TestGbaService;
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Use the {@link CarrierConfigFragment#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class CarrierConfigFragment extends Fragment {
+    private static final String TAG = "CARRIER";
+
+    private static CarrierConfigFragment sInstance;
+
+    private Settings mSettings;
+    private EditText mEditPackageName;
+    private EditText mEditReleaseTime;
+
+    /** get the instance of CarrierConfigFragment */
+    public static CarrierConfigFragment newInstance() {
+        if (sInstance == null) {
+            Log.d(TAG, "new instance:");
+            sInstance = new CarrierConfigFragment();
+        }
+        return sInstance;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mSettings = Settings.getSettings(getActivity());
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        // Inflate the layout for this fragment
+        View viewHierarchy = inflater.inflate(R.layout.fragment_carrier_config, container, false);
+        mEditPackageName = viewHierarchy.findViewById(R.id.editServicePackageName);
+        mEditReleaseTime = viewHierarchy.findViewById(R.id.editServiceReleaseTime);
+        getConfig(false);
+
+        Button buttonDone = viewHierarchy.findViewById(R.id.carrier_config_done);
+        buttonDone.setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        mSettings.updateCarrierConfig(getActivity(),
+                                mEditPackageName.getText().toString(),
+                                Integer.parseInt(mEditReleaseTime.getText().toString()));
+                        getFragmentManager().beginTransaction().remove(
+                                CarrierConfigFragment.this).commitNow();
+                    }
+                }
+        );
+
+        Button buttonClear = viewHierarchy.findViewById(R.id.carrier_config_clear);
+        buttonClear.setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        getConfig(true);
+                    }
+                }
+        );
+        return viewHierarchy;
+    }
+
+    private void getConfig(boolean isDefault) {
+        String packagename = mSettings.getServicePackageName();
+        if (isDefault || packagename == null) {
+            packagename = TestGbaService.class.getPackage().getName();
+        }
+        mEditPackageName.setText(packagename);
+        mEditReleaseTime.setText(isDefault ? "0" : Integer.toString(mSettings.getReleaseTime()));
+    }
+}
diff --git a/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/MainFragment.java b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/MainFragment.java
new file mode 100644
index 0000000..ff50f5c
--- /dev/null
+++ b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/MainFragment.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 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.phone.testapps.gbatestapp.ui.main;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.telephony.TelephonyManager;
+import android.telephony.gba.UaSecurityProtocolIdentifier;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.android.phone.testapps.gbatestapp.R;
+import com.android.phone.testapps.gbatestapp.Settings;
+
+/** main fragent to update settings and run the test */
+public class MainFragment extends Fragment {
+
+    private static final String TAG = "GBATestApp";
+    private static final String TAG_CARRIER = "carrier";
+    private static final String TAG_SERVICE = "service";
+    private static final String TAG_CLIENT = "client";
+
+    private static MainFragment sInstance;
+
+    private Settings mSettings;
+    private TelephonyManager mTelephonyManager;
+
+    /** Get the instance of MainFragment*/
+    public static MainFragment newInstance() {
+        if (sInstance == null) {
+            sInstance = new MainFragment();
+        }
+        return sInstance;
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+                             @Nullable Bundle savedInstanceState) {
+        View viewHierarchy = inflater.inflate(R.layout.main_fragment, container, false);
+
+        Button buttonCarrier = viewHierarchy.findViewById(R.id.carrier_config_change_button);
+        buttonCarrier.setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        Fragment carrierFrag = getChildFragmentManager()
+                                .findFragmentByTag(TAG_CARRIER);
+                        if (carrierFrag == null) {
+                            carrierFrag = CarrierConfigFragment.newInstance();
+                        }
+                        getChildFragmentManager()
+                                .beginTransaction()
+                                .replace(R.id.main, carrierFrag, TAG_CARRIER)
+                                .commitNow();
+                    }
+                }
+        );
+
+        Button buttonService = viewHierarchy.findViewById(R.id.service_config);
+        buttonService.setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        Fragment serviceFrag = getChildFragmentManager()
+                                .findFragmentByTag(TAG_SERVICE);
+                        if (serviceFrag == null) {
+                            serviceFrag = ServiceConfigFragment.newInstance();
+                        }
+                        getChildFragmentManager()
+                                .beginTransaction()
+                                .replace(R.id.main, serviceFrag, TAG_SERVICE)
+                                .commitNow();
+                    }
+                }
+        );
+
+        Button buttonClient = viewHierarchy.findViewById(R.id.client_config);
+        buttonClient.setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        Fragment testFrag = getChildFragmentManager()
+                                .findFragmentByTag(TAG_CLIENT);
+                        if (testFrag == null) {
+                            testFrag = TestConfigFragment.newInstance();
+                        }
+
+                        getChildFragmentManager()
+                                .beginTransaction()
+                                .replace(R.id.main, testFrag, TAG_CLIENT)
+                                .commitNow();
+                    }
+                }
+        );
+
+        Button buttonExit = viewHierarchy.findViewById(R.id.test_exit);
+        buttonExit.setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        getActivity().finish();
+                    }
+                }
+        );
+
+        Button buttonRunning = viewHierarchy.findViewById(R.id.send_request);
+        buttonRunning.setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        Log.d(TAG, "starting test...");
+                        TelephonyManager.BootstrapAuthenticationCallback cb = new
+                                TelephonyManager.BootstrapAuthenticationCallback() {
+                                    @Override
+                                    public void onKeysAvailable(byte[] gbaKey, String btId) {
+                                        String result = "onKeysAvailable, key:"
+                                                + Settings.byteArrayToHexString(gbaKey)
+                                                + ", btid:" + btId;
+                                        Log.d(TAG, result);
+                                        getActivity().runOnUiThread(()-> {
+                                            mTestLog.append(result + "\n");
+                                        });
+                                    }
+
+                                    @Override
+                                    public void onAuthenticationFailure(int reason) {
+                                        String result = "onAuthFailure, cause:" + reason;
+                                        Log.d(TAG, result);
+                                        getActivity().runOnUiThread(
+                                                () -> mTestLog.append(result + "\n"));
+                                    }
+                                };
+                        UaSecurityProtocolIdentifier.Builder builder =
+                                new UaSecurityProtocolIdentifier.Builder();
+                        try {
+                            if (mSettings.getOrg() != 0 || mSettings.getSpId() != 0
+                                    || mSettings.getTlsCs() != 0) {
+                                builder.setOrg(mSettings.getOrg())
+                                        .setProtocol(mSettings.getSpId())
+                                        .setTlsCipherSuite(mSettings.getTlsCs());
+                            }
+                        } catch (IllegalArgumentException e) {
+                            getActivity().runOnUiThread(() -> mTestLog.append(
+                                    "Fail to create UaSecurityProtocolIdentifier " + e + "\n"));
+                            return;
+                        }
+
+                        UaSecurityProtocolIdentifier spId = builder.build();
+                        Log.d(TAG, "bootstrapAuthenticationRequest with parameters [appType:"
+                                + mSettings.getAppType() + ", NAF:" + mSettings.getNafUrl()
+                                + ", spId:" + spId + ", isForceBootstrap:"
+                                + mSettings.isForceBootstrap() + "]");
+                        try {
+                            mTelephonyManager.bootstrapAuthenticationRequest(
+                                    mSettings.getAppType(), Uri.parse(mSettings.getNafUrl()),
+                                    spId, mSettings.isForceBootstrap(),
+                                    AsyncTask.SERIAL_EXECUTOR, cb);
+                        } catch (NullPointerException e) {
+                            getActivity().runOnUiThread(() -> mTestLog.append(
+                                    "Invalid parameters, please check!" + "\n"));
+                        }
+                    }
+                }
+        );
+
+        return viewHierarchy;
+    }
+
+    TextView mTestLog;
+    @Override
+    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        mTelephonyManager = (TelephonyManager)
+                getContext().getSystemService(Context.TELEPHONY_SERVICE);
+        mSettings = Settings.getSettings(getContext());
+        mTestLog = getActivity().findViewById(R.id.viewTestOutput);
+    }
+
+}
diff --git a/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/ServiceConfigFragment.java b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/ServiceConfigFragment.java
new file mode 100644
index 0000000..5e7f2fa
--- /dev/null
+++ b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/ServiceConfigFragment.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 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.phone.testapps.gbatestapp.ui.main;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.phone.testapps.gbatestapp.R;
+import com.android.phone.testapps.gbatestapp.Settings;
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Use the {@link ServiceConfigFragment#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class ServiceConfigFragment extends Fragment {
+
+    private static final String TAG = "SERVICE";
+
+    private static ServiceConfigFragment sInstance;
+
+    private Settings mSettings;
+
+    private CheckBox mAuthResult;
+    private EditText mGbaKey;
+    private EditText mBTid;
+    private EditText mFailReason;
+
+    /** get the instance of ServiceConfigFragment */
+    public static ServiceConfigFragment newInstance() {
+        if (sInstance == null) {
+            sInstance = new ServiceConfigFragment();
+        }
+        return sInstance;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mSettings = Settings.getSettings(getActivity());
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View  viewHierarchy = inflater.inflate(R.layout.fragment_service_config, container, false);
+
+        mAuthResult = viewHierarchy.findViewById(R.id.checkBoxResult);
+        mFailReason = viewHierarchy.findViewById(R.id.editFailReason);
+        mGbaKey = viewHierarchy.findViewById(R.id.editKey);
+        mBTid = viewHierarchy.findViewById(R.id.editBTid);
+
+        setDefault();
+
+        Button buttonDone = viewHierarchy.findViewById(R.id.service_config_done);
+        buttonDone.setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        mSettings.updateServiceConfig(getActivity(), mAuthResult.isChecked(),
+                                Integer.parseInt(mFailReason.getText().toString()),
+                                mGbaKey.getText().toString(), mBTid.getText().toString());
+                        getFragmentManager().beginTransaction()
+                                .remove(ServiceConfigFragment.this).commitNow();
+                    }
+                }
+        );
+
+        Button buttonClear = viewHierarchy.findViewById(R.id.service_config_clear);
+        buttonClear.setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        setDefault();
+                    }
+                }
+        );
+
+        return viewHierarchy;
+    }
+
+    private void setDefault() {
+        mAuthResult.setChecked(mSettings.getAuthResult());
+        String key = mSettings.getGbaKey();
+        if (key == null || key.isEmpty()) {
+            key = getString(R.string.sample_key);
+        }
+        mGbaKey.setText(key);
+        String id = mSettings.getBTid();
+        if (id == null || id.isEmpty()) {
+            id = getString(R.string.sample_btid);
+        }
+        mBTid.setText(id);
+        mFailReason.setText(Integer.toString(mSettings.getFailReason()));
+    }
+}
diff --git a/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/TestConfigFragment.java b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/TestConfigFragment.java
new file mode 100644
index 0000000..4049082
--- /dev/null
+++ b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/TestConfigFragment.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 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.phone.testapps.gbatestapp.ui.main;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.phone.testapps.gbatestapp.R;
+import com.android.phone.testapps.gbatestapp.Settings;
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Use the {@link TestConfigFragment#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class TestConfigFragment extends Fragment {
+
+    private static final String TAG = "TEST_CONFIG";
+
+    private static TestConfigFragment sInstance;
+
+    private Settings mSettings;
+
+    private EditText mAppType;
+    private EditText mUrl;
+    private EditText mOrg;
+    private EditText mSpId;
+    private EditText mTlsCs;
+    private CheckBox mForce;
+
+    /** get the instance of TestConfigFragment */
+    public static TestConfigFragment newInstance() {
+        if (sInstance == null) {
+            sInstance = new TestConfigFragment();
+        }
+        return sInstance;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mSettings = Settings.getSettings(getActivity());
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View viewHierarchy = inflater.inflate(R.layout.fragment_test_config, container, false);
+        mAppType = viewHierarchy.findViewById(R.id.editAppType);
+        mUrl = viewHierarchy.findViewById(R.id.editUrl);
+        mOrg = viewHierarchy.findViewById(R.id.editOrg);
+        mSpId = viewHierarchy.findViewById(R.id.editSpId);
+        mTlsCs = viewHierarchy.findViewById(R.id.editTlsCs);
+        mForce = viewHierarchy.findViewById(R.id.checkBoxForce);
+
+        setDefault();
+
+        Button buttonDone = viewHierarchy.findViewById(R.id.client_config_done);
+        buttonDone.setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        Log.d(TAG, "updateTestConfig");
+                        mSettings.updateTestConfig(getActivity(),
+                                Integer.parseInt(mAppType.getText().toString()),
+                                mUrl.getText().toString(),
+                                mForce.isChecked(),
+                                Integer.parseInt(mOrg.getText().toString()),
+                                Integer.parseInt(mSpId.getText().toString()),
+                                Integer.parseInt(mTlsCs.getText().toString()));
+                        getFragmentManager().beginTransaction().remove(
+                                TestConfigFragment.this).commitNow();
+                    }
+                }
+        );
+
+        Button buttonClear = viewHierarchy.findViewById(R.id.client_config_clear);
+        buttonClear.setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        setDefault();
+                    }
+                }
+        );
+
+        return viewHierarchy;
+    }
+
+    void setDefault() {
+        Log.d(TAG, "setDefault");
+        mAppType.setText(Integer.toString(mSettings.getAppType()));
+        String naf = mSettings.getNafUrl();
+        if (naf == null || naf.isEmpty()) {
+            naf = getString(R.string.sample_naf);
+        }
+        mUrl.setText(naf);
+        mOrg.setText(Integer.toString(mSettings.getOrg()));
+        mSpId.setText(Integer.toString(mSettings.getSpId()));
+        mTlsCs.setText(Integer.toString(mSettings.getTlsCs()));
+        mForce.setChecked(mSettings.isForceBootstrap());
+    }
+}