Move SliceStore notification and activity to a separate app

Android does not allow privileged processes to display a WebView in an
activity. Move the notification logic and SliceStoreActivity to a
separate app and send responses back to SliceStore instead.

Test: atest TelephonyManagerReadNonDangerousPermissionTest
Test: Manually verify notification click, manage, timeout, cancel
Test: Manually verify premium capability throttle, pending, unsupported
Test: Manually verify WebView shows up when activity starts
Test: Manually verify notification/activity behavior with multi user
Bug: 245882092
Bug: 245882601
Change-Id: Ia971d7c859ef281eb13ed4e06a64aba785dcc9d2
diff --git a/Android.bp b/Android.bp
index dad0e6c..5b8af0a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -79,6 +79,13 @@
     },
 }
 
+// Allow other applications to use public constants from SliceStore
+java_library {
+    name: "SliceStore",
+    srcs: ["src/com/android/phone/slicestore/SliceStore.java",],
+    static_libs: ["telephony-common"],
+}
+
 platform_compat_config {
     name: "TeleService-platform-compat-config",
     src: ":TeleService",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d6aacd8..5599a50 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -19,8 +19,11 @@
         package="com.android.phone"
         coreApp="true"
         android:sharedUserId="android.uid.phone"
-        android:sharedUserLabel="@string/phoneAppLabel"
->
+        android:sharedUserLabel="@string/phoneAppLabel">
+
+    <!-- Allows broadcasting for SliceStore events. -->
+    <protected-broadcast android:name="com.android.phone.slicestore.action.START_SLICE_STORE" />
+    <protected-broadcast android:name="com.android.phone.slicestore.action.SLICE_STORE_RESPONSE_TIMEOUT" />
 
     <original-package android:name="com.android.phone" />
     <!-- Allows granting runtime permissions to telephony related components. -->
diff --git a/res/drawable/ic_network_boost.xml b/res/drawable/ic_network_boost.xml
deleted file mode 100644
index b6a7ae1..0000000
--- a/res/drawable/ic_network_boost.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-  ~ 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.
-  -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24"
-        android:tint="?attr/colorControlNormal">
-<path android:fillColor="@android:color/white"
-      android:pathData="M3,17V15H8Q8,15 8,15Q8,15 8,15V13Q8,13 8,13Q8,13 8,13H3V7H10V9H5V11H8Q8.825,11 9.413,11.587Q10,12.175 10,13V15Q10,15.825 9.413,16.413Q8.825,17 8,17ZM21,11V15Q21,15.825 20.413,16.413Q19.825,17 19,17H14Q13.175,17 12.588,16.413Q12,15.825 12,15V9Q12,8.175 12.588,7.587Q13.175,7 14,7H19Q19.825,7 20.413,7.587Q21,8.175 21,9H14Q14,9 14,9Q14,9 14,9V15Q14,15 14,15Q14,15 14,15H19Q19,15 19,15Q19,15 19,15V13H16.5V11Z"/>
-</vector>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cbc99a9..44151c5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2201,15 +2201,4 @@
     <!-- Telephony notification channel name for a channel containing SIP accounts removed
      notificatios -->
     <string name="notification_channel_sip_account">Deprecated SIP accounts</string>
-
-    <!-- Telephony notification channel name for network boost notifications. -->
-    <string name="network_boost_notification_channel">Network Boost</string>
-    <!-- Notification title text for the network boost notification. -->
-    <string name="network_boost_notification_title">%s recommends a data boost</string>
-    <!-- Notification detail text for the network boost notification. -->
-    <string name="network_boost_notification_detail">Buy a network boost for better performance</string>
-    <!-- Notification button text to delay the network boost notification. -->
-    <string name="network_boost_notification_button_delay">Not now</string>
-    <!-- Notification button text to manage the network boost notification. -->
-    <string name="network_boost_notification_button_manage">Manage</string>
 </resources>
diff --git a/src/com/android/phone/slicestore/SliceStore.java b/src/com/android/phone/slicestore/SliceStore.java
index 4bf6874..7d098ca 100644
--- a/src/com/android/phone/slicestore/SliceStore.java
+++ b/src/com/android/phone/slicestore/SliceStore.java
@@ -18,15 +18,12 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.graphics.drawable.Icon;
 import android.net.ConnectivityManager;
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -39,12 +36,14 @@
 import android.telephony.TelephonyManager;
 import android.telephony.data.NetworkSliceInfo;
 import android.telephony.data.NetworkSlicingConfig;
+import android.text.TextUtils;
 import android.util.Log;
 import android.webkit.WebView;
 
 import com.android.internal.telephony.Phone;
-import com.android.phone.R;
 
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -68,6 +67,10 @@
  */
 public class SliceStore extends Handler {
     @NonNull private static final String TAG = "SliceStore";
+
+    /** Value for an invalid premium capability. */
+    public static final int PREMIUM_CAPABILITY_INVALID = -1;
+
     /** Purchasing the premium capability is no longer throttled. */
     private static final int EVENT_PURCHASE_UNTHROTTLED = 1;
     /** Slicing config changed. */
@@ -80,30 +83,78 @@
     /** UUID to report an anomaly when a premium capability is throttled twice in a row. */
     private static final String UUID_CAPABILITY_THROTTLED_TWICE =
             "15574927-e2e2-4593-99d4-2f340d22b383";
-    /** UUID to report an anomaly when the BroadcastReceiver receives an invalid phone ID. */
+    /** UUID to report an anomaly when receiving an invalid phone ID. */
     private static final String UUID_INVALID_PHONE_ID = "ced79f1a-8ac0-4260-8cf3-08b54c0494f3";
-    /** UUID to report an anomaly when the BroadcastReceiver receives an unknown action. */
+    /** UUID to report an anomaly when receiving an unknown action. */
     private static final String UUID_UNKNOWN_ACTION = "0197efb0-dab1-4b0a-abaf-ac9336ec7923";
 
-    /** Channel ID for the network boost notification. */
-    private static final String NETWORK_BOOST_NOTIFICATION_CHANNEL_ID = "network_boost";
-    /** Tag for the network boost notification. */
-    private static final String NETWORK_BOOST_NOTIFICATION_TAG = "SliceStore.Notification";
+    /** Action to start the SliceStore application and display the network boost notification. */
+    public static final String ACTION_START_SLICE_STORE =
+            "com.android.phone.slicestore.action.START_SLICE_STORE";
+    /** Action indicating the SliceStore purchase was not completed in time. */
+    public static final String ACTION_SLICE_STORE_RESPONSE_TIMEOUT =
+            "com.android.phone.slicestore.action.SLICE_STORE_RESPONSE_TIMEOUT";
+    /** Action indicating the network boost notification or WebView was canceled. */
+    private static final String ACTION_SLICE_STORE_RESPONSE_CANCELED =
+            "com.android.phone.slicestore.action.SLICE_STORE_RESPONSE_CANCELED";
+    /** Action indicating a carrier error prevented premium capability purchase. */
+    private static final String ACTION_SLICE_STORE_RESPONSE_CARRIER_ERROR =
+            "com.android.phone.slicestore.action.SLICE_STORE_RESPONSE_CARRIER_ERROR";
+    /** Action indicating a Telephony or SliceStore error prevented premium capability purchase. */
+    private static final String ACTION_SLICE_STORE_RESPONSE_REQUEST_FAILED =
+            "com.android.phone.slicestore.action.SLICE_STORE_RESPONSE_REQUEST_FAILED";
+    /** Action indicating the purchase request was not made on the default data subscription. */
+    private static final String ACTION_SLICE_STORE_RESPONSE_NOT_DEFAULT_DATA =
+            "com.android.phone.slicestore.action.SLICE_STORE_RESPONSE_NOT_DEFAULT_DATA";
 
-    /** Action for when the network boost notification is cancelled. */
-    private static final String ACTION_NOTIFICATION_CANCELED =
-            "com.android.phone.slicestore.action.NOTIFICATION_CANCELED";
-    /** Action for when the user clicks the "Not now" button on the network boost notification. */
-    private static final String ACTION_NOTIFICATION_DELAYED =
-            "com.android.phone.slicestore.action.NOTIFICATION_DELAYED";
-    /** Action for when the user clicks the "Manage" button on the network boost notification. */
-    private static final String ACTION_NOTIFICATION_MANAGE =
-            "com.android.phone.slicestore.action.NOTIFICATION_MANAGE";
-    /** Extra for phone ID to send from the network boost notification. */
-    private static final String EXTRA_PHONE_ID = "com.android.phone.slicestore.extra.PHONE_ID";
-    /** Extra for premium capability to send from the network boost notification. */
-    private static final String EXTRA_PREMIUM_CAPABILITY =
+    /** Extra for the phone index to send to the SliceStore application. */
+    public static final String EXTRA_PHONE_ID = "com.android.phone.slicestore.extra.PHONE_ID";
+    /** Extra for the subscription ID to send to the SliceStore application. */
+    public static final String EXTRA_SUB_ID = "com.android.phone.slicestore.extra.SUB_ID";
+    /** Extra for the requested premium capability to purchase from the SliceStore application. */
+    public static final String EXTRA_PREMIUM_CAPABILITY =
             "com.android.phone.slicestore.extra.PREMIUM_CAPABILITY";
+    /**
+     * Extra for the application name requesting to purchase the premium capability
+     * from the SliceStore application.
+     */
+    public static final String EXTRA_REQUESTING_APP_NAME =
+            "com.android.phone.slicestore.extra.REQUESTING_APP_NAME";
+    /**
+     * Extra for the canceled PendingIntent that the SliceStore application can send as a response
+     * if the network boost notification or WebView was canceled by the user.
+     * Sends {@link #ACTION_SLICE_STORE_RESPONSE_CANCELED}.
+     */
+    public static final String EXTRA_INTENT_CANCELED =
+            "com.android.phone.slicestore.extra.INTENT_CANCELED";
+    /**
+     * Extra for the carrier error PendingIntent that the SliceStore application can send as a
+     * response if the premium capability purchase request failed due to a carrier error.
+     * Sends {@link #ACTION_SLICE_STORE_RESPONSE_CARRIER_ERROR}.
+     */
+    public static final String EXTRA_INTENT_CARRIER_ERROR =
+            "com.android.phone.slicestore.extra.INTENT_CARRIER_ERROR";
+    /**
+     * Extra for the request failed PendingIntent that the SliceStore application can send as a
+     * response if the premium capability purchase request failed due to an error in Telephony or
+     * the SliceStore application.
+     * Sends {@link #ACTION_SLICE_STORE_RESPONSE_REQUEST_FAILED}.
+     */
+    public static final String EXTRA_INTENT_REQUEST_FAILED =
+            "com.android.phone.slicestore.extra.INTENT_REQUEST_FAILED";
+    /**
+     * Extra for the not-default data subscription ID PendingIntent that the SliceStore application
+     * can send as a response if the premium capability purchase request failed because it was not
+     * requested on the default data subscription.
+     * Sends {@link #ACTION_SLICE_STORE_RESPONSE_NOT_DEFAULT_DATA}.
+     */
+    public static final String EXTRA_INTENT_NOT_DEFAULT_DATA =
+            "com.android.phone.slicestore.extra.INTENT_NOT_DEFAULT_DATA";
+
+    /** Component name to send an explicit broadcast to SliceStoreBroadcastReceiver. */
+    private static final ComponentName SLICE_STORE_COMPONENT_NAME =
+            ComponentName.unflattenFromString(
+                    "com.android.carrierdefaultapp/.SliceStoreBroadcastReceiver");
 
     /** Map of phone ID -> SliceStore instances. */
     @NonNull private static final Map<Integer, SliceStore> sInstances = new HashMap<>();
@@ -116,54 +167,80 @@
     @NonNull private final Set<Integer> mThrottledCapabilities = new HashSet<>();
     /** A map of pending capabilities to the onComplete message for the purchase request. */
     @NonNull private final Map<Integer, Message> mPendingPurchaseCapabilities = new HashMap<>();
-    /** A map of capabilities to the CapabilityBroadcastReceiver for the boost notification. */
-    @NonNull private final Map<Integer, CapabilityBroadcastReceiver> mBroadcastReceivers =
+    /** A map of capabilities to the SliceStoreBroadcastReceiver for SliceStore responses. */
+    @NonNull private final Map<Integer, SliceStoreBroadcastReceiver> mSliceStoreBroadcastReceivers =
             new HashMap<>();
     /** The current network slicing configuration. */
     @Nullable private NetworkSlicingConfig mSlicingConfig;
 
-    private final class CapabilityBroadcastReceiver extends BroadcastReceiver {
-        @TelephonyManager.PremiumCapability final int mCapability;
+    private class SliceStoreBroadcastReceiver extends BroadcastReceiver {
+        private final @TelephonyManager.PremiumCapability int mCapability;
 
-        CapabilityBroadcastReceiver(@TelephonyManager.PremiumCapability int capability) {
+        SliceStoreBroadcastReceiver(@TelephonyManager.PremiumCapability int capability) {
             mCapability = capability;
         }
 
         @Override
         public void onReceive(@NonNull Context context, @NonNull Intent intent) {
             String action = intent.getAction();
-            log("CapabilityBroadcastReceiver("
+            logd("SliceStoreBroadcastReceiver("
                     + TelephonyManager.convertPremiumCapabilityToString(mCapability)
                     + ") received action: " + action);
             int phoneId = intent.getIntExtra(EXTRA_PHONE_ID,
                     SubscriptionManager.INVALID_PHONE_INDEX);
-            int capability = intent.getIntExtra(EXTRA_PREMIUM_CAPABILITY, -1);
+            int capability = intent.getIntExtra(EXTRA_PREMIUM_CAPABILITY,
+                    PREMIUM_CAPABILITY_INVALID);
             if (SliceStore.getInstance(phoneId) == null) {
-                String logStr = "CapabilityBroadcastReceiver( "
+                String logStr = "SliceStoreBroadcastReceiver( "
                         + TelephonyManager.convertPremiumCapabilityToString(mCapability)
                         + ") received invalid phoneId: " + phoneId;
                 loge(logStr);
                 AnomalyReporter.reportAnomaly(UUID.fromString(UUID_INVALID_PHONE_ID), logStr);
                 return;
             } else if (capability != mCapability) {
-                log("CapabilityBroadcastReceiver("
+                logd("SliceStoreBroadcastReceiver("
                         + TelephonyManager.convertPremiumCapabilityToString(mCapability)
-                        + ") received invalid capability: "
+                        + ") ignoring intent for capability "
                         + TelephonyManager.convertPremiumCapabilityToString(capability));
                 return;
             }
             switch (action) {
-                case ACTION_NOTIFICATION_CANCELED:
-                    SliceStore.getInstance(phoneId).onUserCanceled(capability);
+                case ACTION_SLICE_STORE_RESPONSE_CANCELED: {
+                    logd("SliceStore canceled for capability: "
+                            + TelephonyManager.convertPremiumCapabilityToString(capability));
+                    SliceStore.getInstance(phoneId).sendPurchaseResultFromSliceStore(capability,
+                            TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED,
+                            true);
                     break;
-                case ACTION_NOTIFICATION_DELAYED:
-                    SliceStore.getInstance(phoneId).onUserDelayed(capability);
+                }
+                case ACTION_SLICE_STORE_RESPONSE_CARRIER_ERROR: {
+                    logd("Carrier error for capability: "
+                            + TelephonyManager.convertPremiumCapabilityToString(capability));
+                    SliceStore.getInstance(phoneId).sendPurchaseResultFromSliceStore(capability,
+                            TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR,
+                            true);
                     break;
-                case ACTION_NOTIFICATION_MANAGE:
-                    SliceStore.getInstance(phoneId).onUserManage(capability);
+                }
+                case ACTION_SLICE_STORE_RESPONSE_REQUEST_FAILED: {
+                    logd("Purchase premium capability request failed for capability: "
+                            + TelephonyManager.convertPremiumCapabilityToString(capability));
+                    SliceStore.getInstance(phoneId).sendPurchaseResultFromSliceStore(capability,
+                            TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED,
+                            false);
                     break;
+                }
+                case ACTION_SLICE_STORE_RESPONSE_NOT_DEFAULT_DATA: {
+                    logd("Purchase premium capability request was not made on the default data "
+                            + "subscription for capability: "
+                            + TelephonyManager.convertPremiumCapabilityToString(capability));
+                    // TODO: change to RESULT_NOT_DEFAULT_DATA after ag/20196642 is merged
+                    SliceStore.getInstance(phoneId).sendPurchaseResultFromSliceStore(capability,
+                            TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED,
+                            false);
+                    break;
+                }
                 default:
-                    String logStr = "CapabilityBroadcastReceiver("
+                    String logStr = "SliceStoreBroadcastReceiver("
                             + TelephonyManager.convertPremiumCapabilityToString(mCapability)
                             + ") received unknown action: " + action;
                     loge(logStr);
@@ -211,7 +288,7 @@
         switch (msg.what) {
             case EVENT_PURCHASE_UNTHROTTLED: {
                 int capability = (int) msg.obj;
-                log("EVENT_PURCHASE_UNTHROTTLED: for capability "
+                logd("EVENT_PURCHASE_UNTHROTTLED: for capability "
                         + TelephonyManager.convertPremiumCapabilityToString(capability));
                 mThrottledCapabilities.remove(capability);
                 break;
@@ -219,25 +296,27 @@
             case EVENT_SLICING_CONFIG_CHANGED: {
                 AsyncResult ar = (AsyncResult) msg.obj;
                 NetworkSlicingConfig config = (NetworkSlicingConfig) ar.result;
-                log("EVENT_SLICING_CONFIG_CHANGED: from " + mSlicingConfig + " to " + config);
+                logd("EVENT_SLICING_CONFIG_CHANGED: from " + mSlicingConfig + " to " + config);
                 mSlicingConfig = config;
                 break;
             }
             case EVENT_DISPLAY_BOOSTER_NOTIFICATION: {
                 int capability = msg.arg1;
                 String appName = (String) msg.obj;
-                log("EVENT_DISPLAY_BOOSTER_NOTIFICATION: " + appName + " requests capability "
+                logd("EVENT_DISPLAY_BOOSTER_NOTIFICATION: " + appName + " requests capability "
                         + TelephonyManager.convertPremiumCapabilityToString(capability));
                 onDisplayBoosterNotification(capability, appName);
                 break;
             }
             case EVENT_PURCHASE_TIMEOUT: {
                 int capability = (int) msg.obj;
-                log("EVENT_PURCHASE_TIMEOUT: for capability "
+                logd("EVENT_PURCHASE_TIMEOUT: for capability "
                         + TelephonyManager.convertPremiumCapabilityToString(capability));
                 onTimeout(capability);
                 break;
             }
+            default:
+                loge("Unknown event: " + msg.obj);
         }
     }
 
@@ -250,20 +329,20 @@
     public boolean isPremiumCapabilityAvailableForPurchase(
             @TelephonyManager.PremiumCapability int capability) {
         if (!arePremiumCapabilitiesSupportedByDevice()) {
-            log("Premium capabilities unsupported by the device.");
+            logd("Premium capabilities unsupported by the device.");
             return false;
         }
         if (!isPremiumCapabilitySupportedByCarrier(capability)) {
-            log("Premium capability "
+            logd("Premium capability "
                     + TelephonyManager.convertPremiumCapabilityToString(capability)
                     + " unsupported by the carrier.");
             return false;
         }
         if (!arePremiumCapabilitiesEnabledByUser()) {
-            log("Premium capabilities disabled by the user.");
+            logd("Premium capabilities disabled by the user.");
             return false;
         }
-        log("Premium capability "
+        logd("Premium capability "
                 + TelephonyManager.convertPremiumCapabilityToString(capability)
                 + " is available for purchase.");
         return true;
@@ -279,7 +358,7 @@
     public synchronized void purchasePremiumCapability(
             @TelephonyManager.PremiumCapability int capability, @NonNull String appName,
             @NonNull Message onComplete) {
-        log("purchasePremiumCapability: " + appName + " requests capability "
+        logd("purchasePremiumCapability: " + appName + " requests capability "
                 + TelephonyManager.convertPremiumCapabilityToString(capability));
         // Check whether the premium capability can be purchased.
         if (!arePremiumCapabilitiesSupportedByDevice()) {
@@ -345,19 +424,30 @@
             @TelephonyManager.PurchasePremiumCapabilityResult int result,
             @NonNull Message onComplete) {
         // Send the onComplete message with the purchase result.
-        log("Purchase result for capability "
+        logd("Purchase result for capability "
                 + TelephonyManager.convertPremiumCapabilityToString(capability)
                 + ": " + TelephonyManager.convertPurchaseResultToString(result));
         AsyncResult.forMessage(onComplete, result, null);
         onComplete.sendToTarget();
     }
 
+    private void sendPurchaseResultFromSliceStore(
+            @TelephonyManager.PremiumCapability int capability,
+            @TelephonyManager.PurchasePremiumCapabilityResult int result, boolean throttle) {
+        mPhone.getContext().unregisterReceiver(mSliceStoreBroadcastReceivers.remove(capability));
+        removeMessages(EVENT_PURCHASE_TIMEOUT, capability);
+        if (throttle) {
+            throttleCapability(capability, getThrottleDuration(result));
+        }
+        sendPurchaseResult(capability, result, mPendingPurchaseCapabilities.remove(capability));
+    }
+
     private void throttleCapability(@TelephonyManager.PremiumCapability int capability,
             long throttleDuration) {
         // Throttle subsequent requests if necessary.
         if (!mThrottledCapabilities.contains(capability)) {
             if (throttleDuration > 0) {
-                log("Throttle purchase requests for capability "
+                logd("Throttle purchase requests for capability "
                         + TelephonyManager.convertPremiumCapabilityToString(capability) + " for "
                         + TimeUnit.MILLISECONDS.toMinutes(throttleDuration) + " minutes.");
                 mThrottledCapabilities.add(capability);
@@ -374,159 +464,70 @@
 
     private void onDisplayBoosterNotification(@TelephonyManager.PremiumCapability int capability,
             @NonNull String appName) {
-        // Start timeout on handler instead of setTimeoutAfter to differentiate cancel and timeout.
+        // Start timeout for purchase completion.
         long timeout = getCarrierConfigs().getLong(CarrierConfigManager
                 .KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG);
-        sendMessageDelayed(obtainMessage(EVENT_PURCHASE_TIMEOUT, capability), timeout);
-
-        log("Display the booster notification for capability "
+        logd("Start purchase timeout for "
                 + TelephonyManager.convertPremiumCapabilityToString(capability) + " for "
                 + TimeUnit.MILLISECONDS.toMinutes(timeout) + " minutes.");
+        sendMessageDelayed(obtainMessage(EVENT_PURCHASE_TIMEOUT, capability), timeout);
 
-        mPhone.getContext().getSystemService(NotificationManager.class).createNotificationChannel(
-                new NotificationChannel(NETWORK_BOOST_NOTIFICATION_CHANNEL_ID,
-                        mPhone.getContext().getResources().getString(
-                                R.string.network_boost_notification_channel),
-                        NotificationManager.IMPORTANCE_DEFAULT));
-        mBroadcastReceivers.put(capability, new CapabilityBroadcastReceiver(capability));
+        // Broadcast start intent to start the SliceStore application
+        Intent intent = new Intent(ACTION_START_SLICE_STORE);
+        intent.setComponent(SLICE_STORE_COMPONENT_NAME);
+        intent.putExtra(EXTRA_PHONE_ID, mPhone.getPhoneId());
+        intent.putExtra(EXTRA_SUB_ID, mPhone.getSubId());
+        intent.putExtra(EXTRA_PREMIUM_CAPABILITY, capability);
+        intent.putExtra(EXTRA_REQUESTING_APP_NAME, appName);
+        intent.putExtra(EXTRA_INTENT_CANCELED,
+                createPendingIntent(ACTION_SLICE_STORE_RESPONSE_CANCELED, capability));
+        intent.putExtra(EXTRA_INTENT_CARRIER_ERROR,
+                createPendingIntent(ACTION_SLICE_STORE_RESPONSE_CARRIER_ERROR, capability));
+        intent.putExtra(EXTRA_INTENT_REQUEST_FAILED,
+                createPendingIntent(ACTION_SLICE_STORE_RESPONSE_REQUEST_FAILED, capability));
+        intent.putExtra(EXTRA_INTENT_NOT_DEFAULT_DATA,
+                createPendingIntent(ACTION_SLICE_STORE_RESPONSE_NOT_DEFAULT_DATA, capability));
+        logd("Broadcasting start intent to SliceStoreBroadcastReceiver.");
+        mPhone.getContext().sendBroadcast(intent);
+
+        // Listen for responses from the SliceStore application
+        mSliceStoreBroadcastReceivers.put(capability, new SliceStoreBroadcastReceiver(capability));
         IntentFilter filter = new IntentFilter();
-        filter.addAction(ACTION_NOTIFICATION_CANCELED);
-        filter.addAction(ACTION_NOTIFICATION_DELAYED);
-        filter.addAction(ACTION_NOTIFICATION_MANAGE);
-        mPhone.getContext().registerReceiver(mBroadcastReceivers.get(capability), filter);
-
-        Notification notification =
-                new Notification.Builder(mPhone.getContext(), NETWORK_BOOST_NOTIFICATION_CHANNEL_ID)
-                .setContentTitle(String.format(mPhone.getContext().getResources().getString(
-                        R.string.network_boost_notification_title), appName))
-                .setContentText(mPhone.getContext().getResources().getString(
-                        R.string.network_boost_notification_detail))
-                .setSmallIcon(R.drawable.ic_network_boost)
-                .setContentIntent(getContentIntent(capability))
-                .setDeleteIntent(getDeleteIntent(capability))
-                .addAction(new Notification.Action.Builder(
-                        Icon.createWithResource(mPhone.getContext(), R.drawable.ic_network_boost),
-                        mPhone.getContext().getResources().getString(
-                                R.string.network_boost_notification_button_delay),
-                        getDelayIntent(capability)).build())
-                .addAction(new Notification.Action.Builder(
-                        Icon.createWithResource(mPhone.getContext(), R.drawable.ic_network_boost),
-                        mPhone.getContext().getResources().getString(
-                                R.string.network_boost_notification_button_manage),
-                        getManageIntent(capability)).build())
-                .setAutoCancel(true)
-                .build();
-
-        mPhone.getContext().getSystemService(NotificationManager.class)
-                .notify(NETWORK_BOOST_NOTIFICATION_TAG, capability, notification);
+        filter.addAction(ACTION_SLICE_STORE_RESPONSE_CANCELED);
+        filter.addAction(ACTION_SLICE_STORE_RESPONSE_CARRIER_ERROR);
+        filter.addAction(ACTION_SLICE_STORE_RESPONSE_REQUEST_FAILED);
+        filter.addAction(ACTION_SLICE_STORE_RESPONSE_NOT_DEFAULT_DATA);
+        mPhone.getContext().registerReceiver(mSliceStoreBroadcastReceivers.get(capability), filter);
     }
 
     /**
-     * Create the content intent for when the user clicks on the network boost notification.
-     * Ths will start the {@link SliceStoreActivity} and display the {@link android.webkit.WebView}
-     * to purchase the premium capability from the carrier.
+     * Create the PendingIntent to allow SliceStore to send back responses.
      *
+     * @param action The action that will be sent for this PendingIntent
      * @param capability The premium capability that was requested.
-     * @return The content intent.
+     * @return The PendingIntent for the given action and capability.
      */
-    @NonNull private PendingIntent getContentIntent(
+    @NonNull private PendingIntent createPendingIntent(@NonNull String action,
             @TelephonyManager.PremiumCapability int capability) {
-        Intent intent = new Intent(mPhone.getContext(), SliceStoreActivity.class);
-        intent.putExtra(EXTRA_PHONE_ID, mPhone.getPhoneId());
-        intent.putExtra(EXTRA_PREMIUM_CAPABILITY, capability);
-        return PendingIntent.getActivity(mPhone.getContext(), 0, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
-    }
-
-    /**
-     * Create the delete intent for when the user cancels the network boost notification.
-     * This will send {@link #ACTION_NOTIFICATION_CANCELED}.
-     *
-     * @param capability The premium capability that was requested.
-     * @return The delete intent.
-     */
-    @NonNull private PendingIntent getDeleteIntent(
-            @TelephonyManager.PremiumCapability int capability) {
-        Intent intent = new Intent(ACTION_NOTIFICATION_CANCELED);
+        Intent intent = new Intent(action);
         intent.putExtra(EXTRA_PHONE_ID, mPhone.getPhoneId());
         intent.putExtra(EXTRA_PREMIUM_CAPABILITY, capability);
         return PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
                 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
     }
 
-    /**
-     * Create the delay intent for when the user clicks the "Not now" button on the network boost
-     * notification. This will send {@link #ACTION_NOTIFICATION_DELAYED}.
-     *
-     * @param capability The premium capability that was requested.
-     * @return The delay intent.
-     */
-    @NonNull private PendingIntent getDelayIntent(
-            @TelephonyManager.PremiumCapability int capability) {
-        Intent intent = new Intent(ACTION_NOTIFICATION_DELAYED);
-        intent.putExtra(EXTRA_PHONE_ID, mPhone.getPhoneId());
-        intent.putExtra(EXTRA_PREMIUM_CAPABILITY, capability);
-        return PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
-                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
-    }
-
-    /**
-     * Create the manage intent for when the user clicks the "Manage" button on the network boost
-     * notification. This will send {@link #ACTION_NOTIFICATION_MANAGE}.
-     *
-     * @param capability The premium capability that was requested.
-     * @return The manage intent.
-     */
-    @NonNull private PendingIntent getManageIntent(
-            @TelephonyManager.PremiumCapability int capability) {
-        Intent intent = new Intent(ACTION_NOTIFICATION_MANAGE);
-        intent.putExtra(EXTRA_PHONE_ID, mPhone.getPhoneId());
-        intent.putExtra(EXTRA_PREMIUM_CAPABILITY, capability);
-        return PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
-                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
-    }
-
-    private void cleanupBoosterNotification(@TelephonyManager.PremiumCapability int capability,
-            @TelephonyManager.PurchasePremiumCapabilityResult int result) {
-        mPhone.getContext().getSystemService(NotificationManager.class)
-                .cancel(NETWORK_BOOST_NOTIFICATION_TAG, capability);
-        mPhone.getContext().unregisterReceiver(mBroadcastReceivers.remove(capability));
-        Message onComplete = mPendingPurchaseCapabilities.remove(capability);
-        throttleCapability(capability, getThrottleDuration(result));
-        sendPurchaseResult(capability, result, onComplete);
-    }
-
     private void onTimeout(@TelephonyManager.PremiumCapability int capability) {
-        log("onTimeout: " + TelephonyManager.convertPremiumCapabilityToString(capability));
-        cleanupBoosterNotification(capability,
-                TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT);
-        // TODO: Cancel SliceStoreActivity as well.
-    }
+        logd("onTimeout: " + TelephonyManager.convertPremiumCapabilityToString(capability));
+        // Broadcast timeout intent to clean up the SliceStore notification and activity
+        Intent intent = new Intent(ACTION_SLICE_STORE_RESPONSE_TIMEOUT);
+        intent.setComponent(SLICE_STORE_COMPONENT_NAME);
+        intent.putExtra(EXTRA_PHONE_ID, mPhone.getPhoneId());
+        intent.putExtra(EXTRA_PREMIUM_CAPABILITY, capability);
+        logd("Broadcasting timeout intent to SliceStoreBroadcastReceiver.");
+        mPhone.getContext().sendBroadcast(intent);
 
-    private void onUserCanceled(@TelephonyManager.PremiumCapability int capability) {
-        log("onUserCanceled: " + TelephonyManager.convertPremiumCapabilityToString(capability));
-        if (hasMessages(EVENT_PURCHASE_TIMEOUT, capability)) {
-            log("onUserCanceled: Removing timeout for capability "
-                    + TelephonyManager.convertPremiumCapabilityToString(capability));
-            removeMessages(EVENT_PURCHASE_TIMEOUT, capability);
-        }
-        cleanupBoosterNotification(capability,
-                TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED);
-    }
-
-    private void onUserDelayed(@TelephonyManager.PremiumCapability int capability) {
-        log("onUserDelayed: " + TelephonyManager.convertPremiumCapabilityToString(capability));
-        // TODO(b/245882092): implement
-    }
-
-    private void onUserManage(@TelephonyManager.PremiumCapability int capability) {
-        log("onUserManage: " + TelephonyManager.convertPremiumCapabilityToString(capability));
-        // TODO(b/245882092): implement
-    }
-
-    private void onUserConfirmed(@TelephonyManager.PremiumCapability int capability) {
-        // TODO(b/245882092, b/245882601): Open webview listening for carrier response
-        //  --> EVENT_CARRIER_SUCCESS or EVENT_CARRIER_ERROR
+        sendPurchaseResultFromSliceStore(
+                capability, TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT, true);
     }
 
     private void onCarrierSuccess(@TelephonyManager.PremiumCapability int capability) {
@@ -534,10 +535,6 @@
         //  Probably need to handle capability expiry as well
     }
 
-    private void onCarrierError(@TelephonyManager.PremiumCapability int capability) {
-        // TODO(b/245882601): Process and return carrier error; throttle
-    }
-
     @Nullable private PersistableBundle getCarrierConfigs() {
         return mPhone.getContext().getSystemService(CarrierConfigManager.class)
                 .getConfigForSubId(mPhone.getSubId());
@@ -559,6 +556,17 @@
 
     private boolean isPremiumCapabilitySupportedByCarrier(
             @TelephonyManager.PremiumCapability int capability) {
+        String url = getCarrierConfigs().getString(
+                CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
+        if (TextUtils.isEmpty(url)) {
+            return false;
+        } else {
+            try {
+                new URL(url);
+            } catch (MalformedURLException e) {
+                return false;
+            }
+        }
         int[] supportedCapabilities = getCarrierConfigs().getIntArray(
                 CarrierConfigManager.KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY);
         if (supportedCapabilities == null) {
@@ -606,7 +614,7 @@
         return true;
     }
 
-    private void log(String s) {
+    private void logd(String s) {
         Log.d(TAG + "-" + mPhone.getPhoneId(), s);
     }
 
diff --git a/src/com/android/phone/slicestore/SliceStoreActivity.java b/src/com/android/phone/slicestore/SliceStoreActivity.java
deleted file mode 100644
index b93f757..0000000
--- a/src/com/android/phone/slicestore/SliceStoreActivity.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.slicestore;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.util.Log;
-
-/**
- * Activity that launches when the user clicks on the network boost notification.
- */
-public class SliceStoreActivity extends Activity {
-    private static final String TAG = "SliceStoreActivity";
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        Log.d(TAG, "onCreate");
-    }
-}