Merge "Add unit tests for SlicePurchaseController"
diff --git a/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java b/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
index de26955..3fa64df 100644
--- a/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
+++ b/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.Context;
 import android.os.PersistableBundle;
 import android.provider.DeviceConfig;
 import android.telephony.AnomalyReporter;
@@ -37,8 +36,12 @@
 
 import java.util.UUID;
 
-class PremiumNetworkEntitlementApi {
-    private static final String TAG = "PremiumNetworkEntitlementApi";
+/**
+ * Premium network entitlement API class to check the premium network slice entitlement result
+ * from carrier API over the network.
+ */
+public class PremiumNetworkEntitlementApi {
+    private static final String TAG = "PremiumNwEntitlementApi";
     private static final String ENTITLEMENT_STATUS_KEY = "EntitlementStatus";
     private static final String PROVISION_STATUS_KEY = "ProvisionStatus";
     private static final String SERVICE_FLOW_URL_KEY = "ServiceFlow_URL";
@@ -61,12 +64,11 @@
             "bypass_eap_aka_auth_for_slice_purchase_enabled";
 
     @NonNull private final Phone mPhone;
-    @NonNull private final PersistableBundle mCarrierConfig;
     @NonNull private final ServiceEntitlement mServiceEntitlement;
 
-    PremiumNetworkEntitlementApi(@NonNull Phone phone, PersistableBundle carrierConfig) {
+    public PremiumNetworkEntitlementApi(@NonNull Phone phone,
+            @NonNull PersistableBundle carrierConfig) {
         mPhone = phone;
-        mCarrierConfig = carrierConfig;
         if (isBypassEapAkaAuthForSlicePurchaseEnabled()) {
             mServiceEntitlement =
                     new ServiceEntitlement(
@@ -89,8 +91,7 @@
      * or {@code null} on unrecoverable network issue or malformed server response.
      * This is blocking call sending HTTP request and should not be called on main thread.
      */
-    @Nullable
-    public PremiumNetworkEntitlementResponse checkEntitlementStatus(
+    @Nullable public PremiumNetworkEntitlementResponse checkEntitlementStatus(
             @TelephonyManager.PremiumCapability int capability) {
         Log.d(TAG, "checkEntitlementStatus subId=" + mPhone.getSubId());
         ServiceEntitlementRequest.Builder requestBuilder = ServiceEntitlementRequest.builder();
@@ -132,24 +133,28 @@
                         return null;
                     }
                     premiumNetworkEntitlementResponse.mEntitlementStatus =
-                            Integer.valueOf(entitlementStatus);
+                            Integer.parseInt(entitlementStatus);
                 }
                 if (jsonToken.has(PROVISION_STATUS_KEY)) {
                     provisionStatus = jsonToken.getString(PROVISION_STATUS_KEY);
                     if (provisionStatus != null) {
                         premiumNetworkEntitlementResponse.mProvisionStatus =
-                                Integer.valueOf(provisionStatus);
+                                Integer.parseInt(provisionStatus);
                     }
                 }
                 if (jsonToken.has(PROVISION_TIME_LEFT_KEY)) {
                     provisionTimeLeft = jsonToken.getString(PROVISION_TIME_LEFT_KEY);
                     if (provisionTimeLeft != null) {
                         premiumNetworkEntitlementResponse.mEntitlementStatus =
-                                Integer.valueOf(provisionTimeLeft);
+                                Integer.parseInt(provisionTimeLeft);
                     }
                 }
                 if (jsonToken.has(SERVICE_FLOW_URL_KEY)) {
                     provisionStatus = jsonToken.getString(SERVICE_FLOW_URL_KEY);
+                    if (provisionStatus != null) {
+                        premiumNetworkEntitlementResponse.mProvisionStatus =
+                                Integer.parseInt(provisionStatus);
+                    }
                     premiumNetworkEntitlementResponse.mServiceFlowURL =
                             jsonToken.getString(SERVICE_FLOW_URL_KEY);
                 }
@@ -160,6 +165,10 @@
             Log.e(TAG, "queryEntitlementStatus failed", e);
             reportAnomaly(UUID_ENTITLEMENT_CHECK_UNEXPECTED_ERROR,
                     "checkEntitlementStatus failed with JSONException");
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "queryEntitlementStatus failed", e);
+            reportAnomaly(UUID_ENTITLEMENT_CHECK_UNEXPECTED_ERROR,
+                    "checkEntitlementStatus failed with NumberFormatException");
         }
 
         return premiumNetworkEntitlementResponse;
@@ -169,31 +178,20 @@
         AnomalyReporter.reportAnomaly(UUID.fromString(uuid), log);
     }
 
-
-    /** Returns carrier config for the {@code subId}. */
-    private static PersistableBundle getConfigForSubId(Context context, int subId) {
-        CarrierConfigManager carrierConfigManager =
-                context.getSystemService(CarrierConfigManager.class);
-        PersistableBundle carrierConfig = carrierConfigManager.getConfigForSubId(subId);
-        if (carrierConfig == null) {
-            Log.d(TAG, "getDefaultConfig");
-            carrierConfig = CarrierConfigManager.getDefaultConfig();
-        }
-        return carrierConfig;
-    }
-
     /**
-     * Returns entitlement server url for the {@code subId} or
-     * a default empty string if it is not available.
+     * Returns entitlement server url from the given carrier configs or a default empty string
+     * if it is not available.
      */
-    public static String getEntitlementServerUrl(PersistableBundle carrierConfig, int subId) {
+    @NonNull public static String getEntitlementServerUrl(
+            @NonNull PersistableBundle carrierConfig) {
         return carrierConfig.getString(
                 CarrierConfigManager.ImsServiceEntitlement.KEY_ENTITLEMENT_SERVER_URL_STRING,
                 "");
     }
 
-    private CarrierConfig getEntitlementServerCarrierConfig(PersistableBundle carrierConfig) {
-        String entitlementServiceUrl = getEntitlementServerUrl(carrierConfig, mPhone.getSubId());
+    @NonNull private CarrierConfig getEntitlementServerCarrierConfig(
+            @NonNull PersistableBundle carrierConfig) {
+        String entitlementServiceUrl = getEntitlementServerUrl(carrierConfig);
         return CarrierConfig.builder().setServerUrl(entitlementServiceUrl).build();
     }
 
diff --git a/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java b/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
index 4588b71..d852a69 100644
--- a/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
+++ b/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
@@ -17,9 +17,13 @@
 package com.android.phone.slice;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 
-class PremiumNetworkEntitlementResponse {
-
+/**
+ * Response class containing the entitlement status, provisioning status, and service flow URL
+ * for premium network entitlement checks.
+ */
+public class PremiumNetworkEntitlementResponse {
     public static final int PREMIUM_NETWORK_ENTITLEMENT_STATUS_DISABLED = 0;
     public static final int PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED = 1;
     public static final int PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCOMPATIBLE = 2;
@@ -50,28 +54,32 @@
             })
     public @interface PremiumNetworkProvisionStatus {}
 
-    @PremiumNetworkEntitlementStatus int mEntitlementStatus;
-    @PremiumNetworkProvisionStatus int mProvisionStatus;
-    int mProvisionTimeLeftInSeconds;
-    String mServiceFlowURL;
+    @PremiumNetworkEntitlementStatus public int mEntitlementStatus;
+    @PremiumNetworkProvisionStatus public int mProvisionStatus;
+    @NonNull public String mServiceFlowURL;
 
-    boolean isProvisioned() {
-        if (mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_PROVISIONED
-                || mEntitlementStatus == PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCLUDED) {
-            return true;
-        }
-        return false;
+    /**
+     * @return {@code true} if the premium network is provisioned and {@code false} otherwise.
+     */
+    public boolean isProvisioned() {
+        return mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_PROVISIONED
+                || mEntitlementStatus == PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCLUDED;
     }
 
-    boolean isProvisioningInProgress() {
-        if (mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_IN_PROGRESS
-                || mEntitlementStatus == PREMIUM_NETWORK_ENTITLEMENT_STATUS_PROVISIONING) {
-            return true;
-        }
-        return false;
+    /**
+     * @return {@code true} if provisioning the premium network is in progress and
+     *         {@code false} otherwise.
+     */
+    public boolean isProvisioningInProgress() {
+        return mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_IN_PROGRESS
+                || mEntitlementStatus == PREMIUM_NETWORK_ENTITLEMENT_STATUS_PROVISIONING;
     }
 
-    boolean isPremiumNetworkCapabilityAllowed() {
+    /**
+     * @return {@code true} if the premium network capability is allowed and
+     *         {@code false} otherwise.
+     */
+    public boolean isPremiumNetworkCapabilityAllowed() {
         switch (mEntitlementStatus) {
             case PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCOMPATIBLE:
             case PREMIUM_NETWORK_ENTITLEMENT_STATUS_DISABLED:
diff --git a/src/com/android/phone/slice/SlicePurchaseController.java b/src/com/android/phone/slice/SlicePurchaseController.java
index 0f29c70..710f27d 100644
--- a/src/com/android/phone/slice/SlicePurchaseController.java
+++ b/src/com/android/phone/slice/SlicePurchaseController.java
@@ -47,6 +47,7 @@
 import android.util.Log;
 import android.webkit.WebView;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
 
 import java.lang.annotation.Retention;
@@ -252,8 +253,8 @@
             mSlicePurchaseControllerBroadcastReceivers = new HashMap<>();
     /** The current network slicing configuration. */
     @Nullable private NetworkSlicingConfig mSlicingConfig;
-    /* Premium network entitlement query API */
-    @NonNull private PremiumNetworkEntitlementApi mPremiumNetworkEntitlementApi;
+    /** Premium network entitlement query API */
+    @NonNull private final PremiumNetworkEntitlementApi mPremiumNetworkEntitlementApi;
 
     private class SlicePurchaseControllerBroadcastReceiver extends BroadcastReceiver {
         @TelephonyManager.PremiumCapability private final int mCapability;
@@ -367,7 +368,8 @@
         return sInstances.get(phoneId);
     }
 
-    private SlicePurchaseController(@NonNull Phone phone, @NonNull Looper looper) {
+    @VisibleForTesting
+    public SlicePurchaseController(@NonNull Phone phone, @NonNull Looper looper) {
         super(looper);
         mPhone = phone;
         // TODO: Create a cached value for slicing config in DataIndication and initialize here
diff --git a/tests/src/com/android/phone/SlicePurchaseControllerTest.java b/tests/src/com/android/phone/SlicePurchaseControllerTest.java
new file mode 100644
index 0000000..f61c66b
--- /dev/null
+++ b/tests/src/com/android/phone/SlicePurchaseControllerTest.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.data.NetworkSliceInfo;
+import android.telephony.data.NetworkSlicingConfig;
+import android.testing.TestableLooper;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Phone;
+import com.android.phone.slice.PremiumNetworkEntitlementApi;
+import com.android.phone.slice.PremiumNetworkEntitlementResponse;
+import com.android.phone.slice.SlicePurchaseController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Map;
+
+@RunWith(AndroidJUnit4.class)
+public class SlicePurchaseControllerTest extends TelephonyTestBase {
+    private static final String TAG = "SlicePurchaseControllerTest";
+    private static final String URL = "file:///android_asset/slice_purchase_test.html";
+    private static final int PHONE_ID = 0;
+    private static final long NOTIFICATION_TIMEOUT = 1000;
+    private static final long PURCHASE_CONDITION_TIMEOUT = 2000;
+    private static final long NETWORK_SETUP_TIMEOUT = 3000;
+    private static final long THROTTLE_TIMEOUT = 4000;
+
+    @Mock Phone mPhone;
+    @Mock Context mMockedContext;
+    @Mock CarrierConfigManager mCarrierConfigManager;
+    @Mock CommandsInterface mCommandsInterface;
+    @Mock ServiceState mServiceState;
+    @Mock PremiumNetworkEntitlementApi mPremiumNetworkEntitlementApi;
+
+    private SlicePurchaseController mSlicePurchaseController;
+    private PersistableBundle mBundle;
+    private PremiumNetworkEntitlementResponse mEntitlementResponse;
+    private Handler mHandler;
+    private TestableLooper mTestableLooper;
+    @TelephonyManager.PurchasePremiumCapabilityResult private int mResult;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        HandlerThread handlerThread = new HandlerThread("SlicePurchaseControllerTest");
+        handlerThread.start();
+        mHandler = new Handler(handlerThread.getLooper()) {
+            @Override
+            public void handleMessage(Message msg) {
+                AsyncResult ar = (AsyncResult) msg.obj;
+                mResult = (int) ar.result;
+            }
+        };
+        mTestableLooper = new TestableLooper(mHandler.getLooper());
+
+        doReturn(PHONE_ID).when(mPhone).getPhoneId();
+        doReturn(mMockedContext).when(mPhone).getContext();
+        doReturn(mServiceState).when(mPhone).getServiceState();
+        mPhone.mCi = mCommandsInterface;
+
+        doReturn(Context.CARRIER_CONFIG_SERVICE).when(mMockedContext)
+                .getSystemServiceName(eq(CarrierConfigManager.class));
+        doReturn(mCarrierConfigManager).when(mMockedContext)
+                .getSystemService(eq(Context.CARRIER_CONFIG_SERVICE));
+        mBundle = new PersistableBundle();
+        doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+
+        mSlicePurchaseController = new SlicePurchaseController(mPhone, mHandler.getLooper());
+        replaceInstance(SlicePurchaseController.class, "sInstances", mSlicePurchaseController,
+                Map.of(PHONE_ID, mSlicePurchaseController));
+        replaceInstance(SlicePurchaseController.class, "mPremiumNetworkEntitlementApi",
+                mSlicePurchaseController, mPremiumNetworkEntitlementApi);
+        mEntitlementResponse = new PremiumNetworkEntitlementResponse();
+        doReturn(mEntitlementResponse).when(mPremiumNetworkEntitlementApi)
+                .checkEntitlementStatus(anyInt());
+    }
+
+    @Test
+    public void testIsPremiumCapabilityAvailableForPurchase() {
+        assertFalse(mSlicePurchaseController.isPremiumCapabilityAvailableForPurchase(
+                TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY));
+
+        // all conditions met
+        doReturn((int) TelephonyManager.NETWORK_TYPE_BITMASK_NR).when(mPhone)
+                .getCachedAllowedNetworkTypesBitmask();
+        mBundle.putIntArray(CarrierConfigManager.KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY,
+                new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY});
+        mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING, URL);
+        doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+        doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mPhone).getSubId();
+
+        // retry to verify available
+        assertTrue(mSlicePurchaseController.isPremiumCapabilityAvailableForPurchase(
+                TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY));
+    }
+
+    @Test
+    public void testPurchasePremiumCapabilityResultFeatureNotSupported() {
+        tryPurchasePremiumCapability();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED,
+                mResult);
+
+        // retry after enabling feature
+        doReturn((int) TelephonyManager.NETWORK_TYPE_BITMASK_NR).when(mPhone)
+                .getCachedAllowedNetworkTypesBitmask();
+
+        tryPurchasePremiumCapability();
+        assertNotEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED,
+                mResult);
+    }
+
+    @Test
+    public void testPurchasePremiumCapabilityResultCarrierDisabled() {
+        doReturn((int) TelephonyManager.NETWORK_TYPE_BITMASK_NR).when(mPhone)
+                .getCachedAllowedNetworkTypesBitmask();
+
+        tryPurchasePremiumCapability();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED, mResult);
+
+        // retry after enabling carrier configs
+        mBundle.putIntArray(CarrierConfigManager.KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY,
+                new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY});
+        mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING, URL);
+        doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+
+        tryPurchasePremiumCapability();
+        assertNotEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED,
+                mResult);
+    }
+
+    @Test
+    public void testPurchasePremiumCapabilityResultNotDefaultDataSubscription() {
+        doReturn((int) TelephonyManager.NETWORK_TYPE_BITMASK_NR).when(mPhone)
+                .getCachedAllowedNetworkTypesBitmask();
+        mBundle.putIntArray(CarrierConfigManager.KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY,
+                new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY});
+        mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING, URL);
+        doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+
+        tryPurchasePremiumCapability();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB,
+                mResult);
+
+        // retry on default data subscription
+        doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mPhone).getSubId();
+
+        tryPurchasePremiumCapability();
+        assertNotEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB,
+                mResult);
+    }
+
+    @Test
+    public void testPurchasePremiumCapabilityResultNetworkNotAvailable() {
+        doReturn((int) TelephonyManager.NETWORK_TYPE_BITMASK_NR).when(mPhone)
+                .getCachedAllowedNetworkTypesBitmask();
+        mBundle.putIntArray(CarrierConfigManager.KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY,
+                new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY});
+        mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING, URL);
+        doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+        doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mPhone).getSubId();
+
+        tryPurchasePremiumCapability();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
+                mResult);
+
+        // retry with valid network
+        doReturn(TelephonyManager.NETWORK_TYPE_NR).when(mServiceState).getDataNetworkType();
+
+        tryPurchasePremiumCapability();
+        assertNotEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
+                mResult);
+    }
+
+    @Test
+    public void testPurchasePremiumCapabilityResultEntitlementCheckFailed() throws Exception {
+        doReturn((int) TelephonyManager.NETWORK_TYPE_BITMASK_NR).when(mPhone)
+                .getCachedAllowedNetworkTypesBitmask();
+        mBundle.putIntArray(CarrierConfigManager.KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY,
+                new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY});
+        mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING, URL);
+        doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+        doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mPhone).getSubId();
+        doReturn(TelephonyManager.NETWORK_TYPE_NR).when(mServiceState).getDataNetworkType();
+        doReturn(null).when(mPremiumNetworkEntitlementApi).checkEntitlementStatus(anyInt());
+
+        tryPurchasePremiumCapability();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED,
+                mResult);
+
+        // retry with provisioned response
+        mEntitlementResponse.mProvisionStatus =
+                PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_PROVISION_STATUS_PROVISIONED;
+        doReturn(mEntitlementResponse).when(mPremiumNetworkEntitlementApi)
+                .checkEntitlementStatus(anyInt());
+
+        tryPurchasePremiumCapability();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED,
+                mResult);
+
+        // retry with provisioning response
+        mEntitlementResponse.mProvisionStatus =
+                PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_PROVISION_STATUS_IN_PROGRESS;
+        doReturn(mEntitlementResponse).when(mPremiumNetworkEntitlementApi)
+                .checkEntitlementStatus(anyInt());
+
+        tryPurchasePremiumCapability();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS,
+                mResult);
+
+        // retry with disallowed response and throttling
+        mEntitlementResponse.mProvisionStatus =
+                PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_PROVISION_STATUS_NOT_PROVISIONED;
+        mEntitlementResponse.mEntitlementStatus =
+                PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCOMPATIBLE;
+        doReturn(mEntitlementResponse).when(mPremiumNetworkEntitlementApi)
+                .checkEntitlementStatus(anyInt());
+        mBundle.putLong(CarrierConfigManager
+                .KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
+                PURCHASE_CONDITION_TIMEOUT);
+        doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+
+        tryPurchasePremiumCapability();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED,
+                mResult);
+
+        // retry to verify throttled
+        tryPurchasePremiumCapability();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, mResult);
+
+        // retry with valid entitlement check to verify unthrottled
+        mTestableLooper.moveTimeForward(PURCHASE_CONDITION_TIMEOUT);
+        mTestableLooper.processAllMessages();
+
+        testPurchasePremiumCapabilityResultSuccess();
+    }
+
+    @Test
+    public void testPurchasePremiumCapabilityResultAlreadyInProgress() {
+        sendValidPurchaseRequest();
+
+        tryPurchasePremiumCapability();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS,
+                mResult);
+
+        // retry to verify same result
+        tryPurchasePremiumCapability();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS,
+                mResult);
+    }
+
+    @Test
+    public void testPurchasePremiumCapabilityResultSuccess() throws Exception {
+        sendValidPurchaseRequest();
+
+        Intent intent = new Intent();
+        intent.setAction("com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_SUCCESS");
+        intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
+        intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+        receiveSlicePurchaseResponse(intent);
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS, mResult);
+
+        // retry tested in testPurchasePremiumCapabilityResultPendingNetworkSetup
+    }
+
+    @Test
+    public void testPurchasePremiumCapabilityResultPendingNetworkSetup() throws Exception {
+        testPurchasePremiumCapabilityResultSuccess();
+
+        tryPurchasePremiumCapability();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP,
+                mResult);
+
+        // retry to verify unthrottled
+        mTestableLooper.moveTimeForward(NETWORK_SETUP_TIMEOUT);
+        mTestableLooper.processAllMessages();
+
+        testPurchasePremiumCapabilityResultSuccess();
+    }
+
+    @Test
+    public void testPurchasePremiumCapabilityResultAlreadyPurchased() throws Exception {
+        testPurchasePremiumCapabilityResultSuccess();
+
+        // TODO: implement slicing config logic properly
+        NetworkSlicingConfig slicingConfig = new NetworkSlicingConfig(Collections.emptyList(),
+                Collections.singletonList(new NetworkSliceInfo.Builder()
+                        .setStatus(NetworkSliceInfo.SLICE_STATUS_ALLOWED).build()));
+        mSlicePurchaseController.obtainMessage(2 /* EVENT_SLICING_CONFIG_CHANGED */,
+                new AsyncResult(null, slicingConfig, null)).sendToTarget();
+        mTestableLooper.processAllMessages();
+
+        tryPurchasePremiumCapability();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED,
+                mResult);
+
+        // retry to verify same result
+        tryPurchasePremiumCapability();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED,
+                mResult);
+
+        // retry to verify purchase expired
+        slicingConfig = new NetworkSlicingConfig(Collections.emptyList(), Collections.emptyList());
+        mSlicePurchaseController.obtainMessage(2 /* EVENT_SLICING_CONFIG_CHANGED */,
+                new AsyncResult(null, slicingConfig, null)).sendToTarget();
+        mTestableLooper.processAllMessages();
+
+        testPurchasePremiumCapabilityResultSuccess();
+    }
+
+    @Test
+    public void testPurchasePremiumCapabilityResultTimeout() throws Exception {
+        sendValidPurchaseRequest();
+
+        mTestableLooper.moveTimeForward(NOTIFICATION_TIMEOUT);
+        mTestableLooper.processAllMessages();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT, mResult);
+
+        // retry to verify throttled
+        tryPurchasePremiumCapability();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, mResult);
+
+        // retry to verify unthrottled
+        mTestableLooper.moveTimeForward(THROTTLE_TIMEOUT);
+        mTestableLooper.processAllMessages();
+
+        testPurchasePremiumCapabilityResultSuccess();
+    }
+
+    @Test
+    public void testPurchasePremiumCapabilityResultUserCanceled() throws Exception {
+        sendValidPurchaseRequest();
+
+        Intent intent = new Intent();
+        intent.setAction("com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_CANCELED");
+        intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
+        intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+        receiveSlicePurchaseResponse(intent);
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED, mResult);
+
+        // retry to verify throttled
+        tryPurchasePremiumCapability();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, mResult);
+
+        // retry to verify unthrottled
+        mTestableLooper.moveTimeForward(THROTTLE_TIMEOUT);
+        mTestableLooper.processAllMessages();
+
+        testPurchasePremiumCapabilityResultSuccess();
+    }
+
+    @Test
+    public void testPurchasePremiumCapabilityResultCarrierError() throws Exception {
+        sendValidPurchaseRequest();
+
+        Intent intent = new Intent();
+        intent.setAction(
+                "com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_CARRIER_ERROR");
+        intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
+        intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+        intent.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE,
+                SlicePurchaseController.FAILURE_CODE_SERVER_UNREACHABLE);
+        receiveSlicePurchaseResponse(intent);
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR, mResult);
+
+        // retry to verify throttled
+        tryPurchasePremiumCapability();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, mResult);
+
+        // retry to verify unthrottled
+        mTestableLooper.moveTimeForward(PURCHASE_CONDITION_TIMEOUT);
+        mTestableLooper.processAllMessages();
+
+        testPurchasePremiumCapabilityResultSuccess();
+    }
+
+    @Test
+    public void testPurchasePremiumCapabilityResultRequestFailed() throws Exception {
+        sendValidPurchaseRequest();
+
+        Intent intent = new Intent();
+        intent.setAction(
+                "com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_REQUEST_FAILED");
+        intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
+        intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+        receiveSlicePurchaseResponse(intent);
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED, mResult);
+
+        // retry to verify no throttling
+        testPurchasePremiumCapabilityResultSuccess();
+    }
+
+    @Test
+    public void testPurchasePremiumCapabilityResultNotDefaultDataSubscriptionResponse()
+            throws Exception {
+        sendValidPurchaseRequest();
+
+        Intent intent = new Intent();
+        intent.setAction(
+                "com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUB");
+        intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
+        intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+        receiveSlicePurchaseResponse(intent);
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB,
+                mResult);
+
+        // retry to verify no throttling
+        testPurchasePremiumCapabilityResultSuccess();
+    }
+
+    private void sendValidPurchaseRequest() {
+        // feature supported
+        doReturn((int) TelephonyManager.NETWORK_TYPE_BITMASK_NR).when(mPhone)
+                .getCachedAllowedNetworkTypesBitmask();
+        // carrier supported
+        mBundle.putIntArray(CarrierConfigManager.KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY,
+                new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY});
+        mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING, URL);
+        mBundle.putLong(CarrierConfigManager
+                .KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG,
+                NOTIFICATION_TIMEOUT);
+        mBundle.putLong(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG,
+                NETWORK_SETUP_TIMEOUT);
+        mBundle.putLong(CarrierConfigManager
+                .KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
+                THROTTLE_TIMEOUT);
+        mBundle.putLong(CarrierConfigManager
+                .KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
+                PURCHASE_CONDITION_TIMEOUT);
+        doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+        // default data subscription
+        doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mPhone).getSubId();
+        // network available
+        doReturn(TelephonyManager.NETWORK_TYPE_NR).when(mServiceState).getDataNetworkType();
+        // entitlement check passed
+        mEntitlementResponse.mEntitlementStatus =
+                PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED;
+        doReturn(mEntitlementResponse).when(mPremiumNetworkEntitlementApi)
+                .checkEntitlementStatus(anyInt());
+
+        tryPurchasePremiumCapability();
+        // verify that the purchase request was sent successfully and purchase timeout started
+        assertTrue(mSlicePurchaseController.hasMessages(4 /* EVENT_PURCHASE_TIMEOUT */,
+                TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY));
+    }
+
+    private void tryPurchasePremiumCapability() {
+        try {
+            mSlicePurchaseController.purchasePremiumCapability(
+                    TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, TAG,
+                    mHandler.obtainMessage());
+            mTestableLooper.processAllMessages();
+        } catch (Exception expected) {
+            // SecurityException rethrown as RuntimeException when broadcasting intent
+            Log.d(TAG, expected.getMessage());
+        }
+        mTestableLooper.processAllMessages();
+    }
+
+    private void receiveSlicePurchaseResponse(Intent intent) throws Exception {
+        Class<?> receiverClass = Class.forName("com.android.phone.slice.SlicePurchaseController"
+                + "$SlicePurchaseControllerBroadcastReceiver");
+        Constructor<?> constructor = receiverClass.getDeclaredConstructor(
+                SlicePurchaseController.class, int.class);
+        constructor.setAccessible(true);
+        Object receiver = constructor.newInstance(mSlicePurchaseController,
+                TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+
+        Method method = receiver.getClass().getDeclaredMethod(
+                "onReceive", Context.class, Intent.class);
+        method.invoke(receiver, mMockedContext, intent);
+        mTestableLooper.processAllMessages();
+    }
+}