Add support for network boost purchase, failure, and expiry

Create a JavaScript interface allowing websites to send responses to
SliceStore
Add support for premium capability purchase, failure, and expiry
Create new result for success but pending network setup

Bug: 245882601
Test: manual test response, purchase, expiry, failure
Test: atest TelephonyManagerReadNonDangerousPermissionTest#testPremiumCapabilities
Change-Id: I51fc674f37679fb0eb1314e7b0939555d0767f87
diff --git a/core/api/current.txt b/core/api/current.txt
index 218d7bd..23ff8e1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -41754,6 +41754,7 @@
     field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool";
     field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
     field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY = "premium_capability_maximum_notification_count_int_array";
+    field public static final String KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG = "premium_capability_network_setup_time_millis_long";
     field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = "premium_capability_notification_backoff_hysteresis_time_millis_long";
     field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG = "premium_capability_notification_display_timeout_millis_long";
     field public static final String KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = "premium_capability_purchase_condition_backoff_hysteresis_time_millis_long";
@@ -44048,6 +44049,7 @@
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12; // 0xc
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA = 14; // 0xe
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN = 5; // 0x5
+    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15; // 0xf
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED = 11; // 0xb
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS = 1; // 0x1
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2; // 0x2
diff --git a/packages/CarrierDefaultApp/Android.bp b/packages/CarrierDefaultApp/Android.bp
index 6990ad0..62ffe38 100644
--- a/packages/CarrierDefaultApp/Android.bp
+++ b/packages/CarrierDefaultApp/Android.bp
@@ -13,4 +13,9 @@
     libs: ["SliceStore"],
     platform_apis: true,
     certificate: "platform",
+    optimize: {
+        proguard_flags_files: [
+            "proguard.flags",
+        ],
+    },
 }
diff --git a/packages/CarrierDefaultApp/assets/slice_store_test.html b/packages/CarrierDefaultApp/assets/slice_store_test.html
new file mode 100644
index 0000000..7ddbd2d
--- /dev/null
+++ b/packages/CarrierDefaultApp/assets/slice_store_test.html
@@ -0,0 +1,78 @@
+<!--
+  ~ 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.
+  -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="description" content="
+    This is a HTML page that calls and verifies responses from the @JavascriptInterface functions of
+    SliceStoreWebInterface. Test SliceStore APIs using ADB shell commands and the APIs below:
+
+    FROM TERMINAL:
+    Allow device to override carrier configs:
+    $ adb root
+    Set PREMIUM_CAPABILITY_PRIORITIZE_LATENCY enabled:
+    $ adb shell cmd phone cc set-value -p supported_premium_capabilities_int_array 34
+    Set the carrier purchase URL to this test HTML file:
+    $ adb shell cmd phone cc set-value -p premium_capability_purchase_url_string \
+      file:///android_asset/slice_store_test.html
+    OPTIONAL: Allow premium capability purchase on LTE:
+    $ adb shell cmd phone cc set-value -p premium_capability_supported_on_lte_bool true
+    OPTIONAL: Override ServiceState to fake a NR SA connection:
+    $ adb shell am broadcast -a com.android.internal.telephony.TestServiceState --ei data_rat 20
+
+    FROM TEST ACTIVITY:
+    TelephonyManager tm = getApplicationContext().getSystemService(TelephonyManager.class)
+    tm.isPremiumCapabilityAvailable(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+    LinkedBlockingQueue<Integer> purchaseRequests = new LinkedBlockingQueue<>();
+    tm.purchasePremiumCapability(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY,
+            this.getMainExecutor(), request::offer);
+
+    When the test application starts, this HTML will be loaded into the WebView along with the
+    associated JavaScript functions in file:///android_asset/slice_store_test.js.
+    Click on the buttons in the HTML to call the corresponding @JavascriptInterface APIs.
+
+    RESET DEVICE STATE:
+    Clear carrier configurations that were set:
+    $ adb shell cmd phone cc clear-values
+    Clear ServiceState override that was set:
+    $ adb shell am broadcast -a com.android.internal.telephony.TestServiceState --es action reset
+    ">
+    <title>Test SliceStoreActivity</title>
+    <script type="text/javascript" src="slice_store_test.js"></script>
+</head>
+<body>
+    <h1>Test SliceStoreActivity</h1>
+    <h2>Get requested premium capability</h2>
+    <button type="button" onclick="testGetRequestedCapability()">
+        Get requested premium capability
+    </button>
+    <p id="requested_capability"></p>
+
+    <h2>Notify purchase successful</h2>
+    <button type="button" onclick="testNotifyPurchaseSuccessful(60000)">
+        Notify purchase successful for 1 minute
+    </button>
+    <p id="purchase_successful"></p>
+
+    <h2>Notify purchase failed</h2>
+    <button type="button" onclick="testNotifyPurchaseFailed()">
+        Notify purchase failed
+    </button>
+    <p id="purchase_failed"></p>
+</body>
+</html>
diff --git a/packages/CarrierDefaultApp/assets/slice_store_test.js b/packages/CarrierDefaultApp/assets/slice_store_test.js
new file mode 100644
index 0000000..f12a6da
--- /dev/null
+++ b/packages/CarrierDefaultApp/assets/slice_store_test.js
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+function testGetRequestedCapability() {
+    let capability = SliceStoreWebInterface.getRequestedCapability();
+    document.getElementById("requested_capability").innerHTML =
+            "Premium capability requested: " + capability;
+}
+
+function testNotifyPurchaseSuccessful(duration_ms_long = 0) {
+    SliceStoreWebInterface.notifyPurchaseSuccessful(duration);
+    document.getElementById("purchase_successful").innerHTML =
+            "Notified purchase success for duration: " + duration;
+}
+
+function testNotifyPurchaseFailed() {
+    SliceStoreWebInterface.notifyPurchaseFailed();
+    document.getElementById("purchase_failed").innerHTML =
+            "Notified purchase failed.";
+}
diff --git a/packages/CarrierDefaultApp/proguard.flags b/packages/CarrierDefaultApp/proguard.flags
new file mode 100644
index 0000000..64fec2c
--- /dev/null
+++ b/packages/CarrierDefaultApp/proguard.flags
@@ -0,0 +1,4 @@
+# Keep classes and methods that have the @JavascriptInterface annotation
+-keepclassmembers class * {
+    @android.webkit.JavascriptInterface <methods>;
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
index 602e31c..348e389 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
@@ -20,47 +20,63 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.NotificationManager;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
+import android.view.KeyEvent;
 import android.webkit.WebView;
 
 import com.android.phone.slicestore.SliceStore;
 
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Activity that launches when the user clicks on the network boost notification.
+ * This will open a {@link WebView} for the carrier website to allow the user to complete the
+ * premium capability purchase.
+ * The carrier website can get the requested premium capability using the JavaScript interface
+ * method {@code SliceStoreWebInterface.getRequestedCapability()}.
+ * If the purchase is successful, the carrier website shall notify SliceStore using the JavaScript
+ * interface method {@code SliceStoreWebInterface.notifyPurchaseSuccessful(duration)}, where
+ * {@code duration} is the duration of the network boost.
+ * If the purchase was not successful, the carrier website shall notify SliceStore using the
+ * JavaScript interface method {@code SliceStoreWebInterface.notifyPurchaseFailed()}.
+ * If either of these notification methods are not called, the purchase cannot be completed
+ * successfully and the purchase request will eventually time out.
  */
 public class SliceStoreActivity extends Activity {
     private static final String TAG = "SliceStoreActivity";
 
-    private URL mUrl;
-    private WebView mWebView;
-    private int mPhoneId;
+    private @NonNull WebView mWebView;
+    private @NonNull Context mApplicationContext;
     private int mSubId;
-    private @TelephonyManager.PremiumCapability int mCapability;
+    @TelephonyManager.PremiumCapability protected int mCapability;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         Intent intent = getIntent();
-        mPhoneId = intent.getIntExtra(SliceStore.EXTRA_PHONE_ID,
-                SubscriptionManager.INVALID_PHONE_INDEX);
         mSubId = intent.getIntExtra(SliceStore.EXTRA_SUB_ID,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         mCapability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
                 SliceStore.PREMIUM_CAPABILITY_INVALID);
-        mUrl = getUrl();
-        logd("onCreate: mPhoneId=" + mPhoneId + ", mSubId=" + mSubId + ", mCapability="
+        mApplicationContext = getApplicationContext();
+        URL url = getUrl();
+        logd("onCreate: subId=" + mSubId + ", capability="
                 + TelephonyManager.convertPremiumCapabilityToString(mCapability)
-                + ", mUrl=" + mUrl);
-        getApplicationContext().getSystemService(NotificationManager.class)
+                + ", url=" + url);
+
+        // Cancel network boost notification
+        mApplicationContext.getSystemService(NotificationManager.class)
                 .cancel(SliceStoreBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability);
+
+        // Verify intent and values are valid
         if (!SliceStoreBroadcastReceiver.isIntentValid(intent)) {
             loge("Not starting SliceStoreActivity with an invalid Intent: " + intent);
             SliceStoreBroadcastReceiver.sendSliceStoreResponse(
@@ -68,10 +84,15 @@
             finishAndRemoveTask();
             return;
         }
-        if (mUrl == null) {
-            loge("Unable to create a URL from carrier configs.");
-            SliceStoreBroadcastReceiver.sendSliceStoreResponse(
-                    intent, SliceStore.EXTRA_INTENT_CARRIER_ERROR);
+        if (url == null) {
+            String error = "Unable to create a URL from carrier configs.";
+            loge(error);
+            Intent data = new Intent();
+            data.putExtra(SliceStore.EXTRA_FAILURE_CODE,
+                    SliceStore.FAILURE_CODE_CARRIER_URL_UNAVAILABLE);
+            data.putExtra(SliceStore.EXTRA_FAILURE_REASON, error);
+            SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData(
+                    mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_CARRIER_ERROR, data);
             finishAndRemoveTask();
             return;
         }
@@ -83,12 +104,53 @@
             return;
         }
 
+        // Create a reference to this activity in SliceStoreBroadcastReceiver
         SliceStoreBroadcastReceiver.updateSliceStoreActivity(mCapability, this);
 
+        // Create and configure WebView
         mWebView = new WebView(this);
+        // Enable JavaScript for the carrier purchase website to send results back to SliceStore
+        mWebView.getSettings().setJavaScriptEnabled(true);
+        mWebView.addJavascriptInterface(new SliceStoreWebInterface(this), "SliceStoreWebInterface");
+
+        // Display WebView
         setContentView(mWebView);
-        mWebView.loadUrl(mUrl.toString());
-        // TODO(b/245882601): Get back response from WebView
+        mWebView.loadUrl(url.toString());
+    }
+
+    protected void onPurchaseSuccessful(long duration) {
+        logd("onPurchaseSuccessful: Carrier website indicated successfully purchased premium "
+                + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability)
+                + " for " + TimeUnit.MILLISECONDS.toMinutes(duration) + " minutes.");
+        Intent intent = new Intent();
+        intent.putExtra(SliceStore.EXTRA_PURCHASE_DURATION, duration);
+        SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData(
+                mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_SUCCESS, intent);
+        finishAndRemoveTask();
+    }
+
+    protected void onPurchaseFailed(@SliceStore.FailureCode int failureCode,
+            @Nullable String failureReason) {
+        logd("onPurchaseFailed: Carrier website indicated purchase failed for premium capability "
+                + TelephonyManager.convertPremiumCapabilityToString(mCapability) + " with code: "
+                + SliceStore.convertFailureCodeToString(failureCode) + " and reason: "
+                + failureReason);
+        Intent data = new Intent();
+        data.putExtra(SliceStore.EXTRA_FAILURE_CODE, failureCode);
+        data.putExtra(SliceStore.EXTRA_FAILURE_REASON, failureReason);
+        SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData(
+                mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_CARRIER_ERROR, data);
+        finishAndRemoveTask();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
+        // Pressing back in the WebView will go to the previous page instead of closing SliceStore.
+        if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {
+            mWebView.goBack();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
     }
 
     @Override
@@ -100,8 +162,8 @@
         super.onDestroy();
     }
 
-    private @Nullable URL getUrl() {
-        String url = getApplicationContext().getSystemService(CarrierConfigManager.class)
+    @Nullable private URL getUrl() {
+        String url = mApplicationContext.getSystemService(CarrierConfigManager.class)
                 .getConfigForSubId(mSubId).getString(
                         CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
         try {
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
index 7eb851d..7867ef1 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
@@ -26,6 +26,7 @@
 import android.content.Intent;
 import android.graphics.drawable.Icon;
 import android.os.UserHandle;
+import android.telephony.AnomalyReporter;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -37,6 +38,7 @@
 import java.lang.ref.WeakReference;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.UUID;
 
 /**
  * The SliceStoreBroadcastReceiver listens for {@link SliceStore#ACTION_START_SLICE_STORE} from the
@@ -47,6 +49,12 @@
 public class SliceStoreBroadcastReceiver extends BroadcastReceiver{
     private static final String TAG = "SliceStoreBroadcastReceiver";
 
+    /**
+     * UUID to report an anomaly when receiving a PendingIntent from an application or process
+     * other than the Phone process.
+     */
+    private static final String UUID_BAD_PENDING_INTENT = "c360246e-95dc-4abf-9dc1-929a76cd7e53";
+
     /** Weak references to {@link SliceStoreActivity} for each capability, if it exists. */
     private static final Map<Integer, WeakReference<SliceStoreActivity>> sSliceStoreActivities =
             new HashMap<>();
@@ -102,6 +110,28 @@
     }
 
     /**
+     * Send the PendingIntent containing the corresponding SliceStore response with additional data.
+     *
+     * @param context The Context to use to send the PendingIntent.
+     * @param intent The Intent containing the PendingIntent extra.
+     * @param extra The extra to get the PendingIntent to send.
+     * @param data The Intent containing additional data to send with the PendingIntent.
+     */
+    public static void sendSliceStoreResponseWithData(@NonNull Context context,
+            @NonNull Intent intent, @NonNull String extra, @NonNull Intent data) {
+        PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class);
+        if (pendingIntent == null) {
+            loge("PendingIntent does not exist for extra: " + extra);
+            return;
+        }
+        try {
+            pendingIntent.send(context, 0 /* unused */, data);
+        } catch (PendingIntent.CanceledException e) {
+            loge("Unable to send " + getPendingIntentType(extra) + " intent: " + e);
+        }
+    }
+
+    /**
      * Check whether the Intent is valid and can be used to complete purchases in the SliceStore.
      * This checks that all necessary extras exist and that the values are valid.
      *
@@ -139,7 +169,8 @@
         return isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_CANCELED)
                 && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_CARRIER_ERROR)
                 && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_REQUEST_FAILED)
-                && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA);
+                && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA)
+                && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_SUCCESS);
     }
 
     private static boolean isPendingIntentValid(@NonNull Intent intent, @NonNull String extra) {
@@ -148,12 +179,20 @@
         if (pendingIntent == null) {
             loge("isPendingIntentValid: " + intentType + " intent not found.");
             return false;
-        } else if (pendingIntent.getCreatorPackage().equals(TelephonyManager.PHONE_PROCESS_NAME)) {
-            return true;
         }
-        loge("isPendingIntentValid: " + intentType + " intent was created by "
-                + pendingIntent.getCreatorPackage() + " instead of the phone process.");
-        return false;
+        String creatorPackage = pendingIntent.getCreatorPackage();
+        if (!creatorPackage.equals(TelephonyManager.PHONE_PROCESS_NAME)) {
+            String logStr = "isPendingIntentValid: " + intentType + " intent was created by "
+                    + creatorPackage + " instead of the phone process.";
+            loge(logStr);
+            AnomalyReporter.reportAnomaly(UUID.fromString(UUID_BAD_PENDING_INTENT), logStr);
+            return false;
+        }
+        if (!pendingIntent.isBroadcast()) {
+            loge("isPendingIntentValid: " + intentType + " intent is not a broadcast.");
+            return false;
+        }
+        return true;
     }
 
     @NonNull private static String getPendingIntentType(@NonNull String extra) {
@@ -162,6 +201,7 @@
             case SliceStore.EXTRA_INTENT_CARRIER_ERROR: return "carrier error";
             case SliceStore.EXTRA_INTENT_REQUEST_FAILED: return "request failed";
             case SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA: return "not default data";
+            case SliceStore.EXTRA_INTENT_SUCCESS: return "success";
             default: {
                 loge("Unknown pending intent extra: " + extra);
                 return "unknown(" + extra + ")";
@@ -292,7 +332,6 @@
             logd("Closing SliceStore WebView since the user did not complete the purchase "
                     + "in time.");
             sSliceStoreActivities.get(capability).get().finishAndRemoveTask();
-            // TODO: Display a toast to indicate timeout for better UX?
         }
     }
 
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java
new file mode 100644
index 0000000..ab5d080
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java
@@ -0,0 +1,90 @@
+/*
+ * 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.carrierdefaultapp;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.telephony.TelephonyManager;
+import android.webkit.JavascriptInterface;
+
+import com.android.phone.slicestore.SliceStore;
+
+/**
+ * SliceStore web interface class allowing carrier websites to send responses back to SliceStore
+ * using JavaScript.
+ */
+public class SliceStoreWebInterface {
+    @NonNull SliceStoreActivity mActivity;
+
+    public SliceStoreWebInterface(@NonNull SliceStoreActivity activity) {
+        mActivity = activity;
+    }
+    /**
+     * Interface method allowing the carrier website to get the premium capability
+     * that was requested to purchase.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
+     *     function getRequestedCapability(duration) {
+     *         SliceStoreWebInterface.getRequestedCapability();
+     *     }
+     * </script>
+     */
+    @JavascriptInterface
+    @TelephonyManager.PremiumCapability public int getRequestedCapability() {
+        return mActivity.mCapability;
+    }
+
+    /**
+     * Interface method allowing the carrier website to notify the SliceStore of a successful
+     * premium capability purchase and the duration for which the premium capability is purchased.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
+     *     function notifyPurchaseSuccessful(duration_ms_long = 0) {
+     *         SliceStoreWebInterface.notifyPurchaseSuccessful(duration_ms_long);
+     *     }
+     * </script>
+     *
+     * @param duration The duration for which the premium capability is purchased in milliseconds.
+     */
+    @JavascriptInterface
+    public void notifyPurchaseSuccessful(long duration) {
+        mActivity.onPurchaseSuccessful(duration);
+    }
+
+    /**
+     * Interface method allowing the carrier website to notify the SliceStore of a failed
+     * premium capability purchase.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
+     *     function notifyPurchaseFailed() {
+     *         SliceStoreWebInterface.notifyPurchaseFailed();
+     *     }
+     * </script>
+     *
+     * @param failureCode The failure code.
+     * @param failureReason If the failure code is {@link SliceStore#FAILURE_CODE_UNKNOWN},
+     *                      the human-readable reason for failure.
+     */
+    @JavascriptInterface
+    public void notifyPurchaseFailed(@SliceStore.FailureCode int failureCode,
+            @Nullable String failureReason) {
+        mActivity.onPurchaseFailed(failureCode, failureReason);
+    }
+}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d314a65..246488e 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8758,6 +8758,22 @@
             "premium_capability_purchase_condition_backoff_hysteresis_time_millis_long";
 
     /**
+     * The amount of time in milliseconds within which the network must set up a slicing
+     * configuration for the premium capability after
+     * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
+     * returns {@link TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS}.
+     * During the setup time, calls to
+     * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)} will return
+     * {@link TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP}.
+     * If the network fails set up a slicing configuration for the premium capability within the
+     * setup time, subsequent purchase requests will be allowed to go through again.
+     *
+     * The default value is 5 minutes.
+     */
+    public static final String KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG =
+            "premium_capability_network_setup_time_millis_long";
+
+    /**
      * The URL to redirect to when the user clicks on the notification for a network boost via
      * premium capabilities after applications call
      * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}.
@@ -9474,6 +9490,8 @@
         sDefaults.putLong(
                 KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
                 TimeUnit.MINUTES.toMillis(30));
+        sDefaults.putLong(KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG,
+                TimeUnit.MINUTES.toMillis(5));
         sDefaults.putString(KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING, null);
         sDefaults.putBoolean(KEY_PREMIUM_CAPABILITY_SUPPORTED_ON_LTE_BOOL, false);
         sDefaults.putStringArray(KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY, new String[]{
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ff1b1c0..d8e5fa2 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17195,7 +17195,13 @@
     }
 
     /**
-     * Purchase premium capability request was successful. Subsequent attempts will return
+     * Purchase premium capability request was successful.
+     * Once the purchase result is successful, the network must set up a slicing configuration
+     * for the purchased premium capability within the timeout specified by
+     * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG}.
+     * During the setup time, subsequent attempts will return
+     * {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP}.
+     * After setup is complete, subsequent attempts will return
      * {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED} until the booster expires.
      * The expiry time is determined by the type or duration of boost purchased from the carrier,
      * provided at {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING}.
@@ -17319,6 +17325,16 @@
     public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA = 14;
 
     /**
+     * Purchase premium capability was successful and is waiting for the network to setup the
+     * slicing configuration. If the setup is complete within the time specified by
+     * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG},
+     * subsequent requests will return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED}
+     * until the purchase expires. If the setup is not complete within the time specified above,
+     * applications can reques the premium capability again.
+     */
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15;
+
+    /**
      * Results of the purchase premium capability request.
      * @hide
      */
@@ -17336,7 +17352,8 @@
             PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED,
-            PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA})
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP})
     public @interface PurchasePremiumCapabilityResult {}
 
     /**
@@ -17377,6 +17394,8 @@
                 return "NETWORK_CONGESTED";
             case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA:
                 return "NOT_DEFAULT_DATA";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP:
+                return "PENDING_NETWORK_SETUP";
             default:
                 return "UNKNOWN (" + result + ")";
         }