Merge "Fix flaky NotificationMgrTests"
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index fd8a7ee..6cbc051 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -579,7 +579,7 @@
     <string name="onscreenEndCallText" msgid="6138725377654842757">"Finalizar"</string>
     <string name="onscreenShowDialpadText" msgid="658465753816164079">"Teclado"</string>
     <string name="onscreenMuteText" msgid="5470306116733843621">"Desativar som"</string>
-    <string name="onscreenAddCallText" msgid="9075675082903611677">"Adicionar chamada"</string>
+    <string name="onscreenAddCallText" msgid="9075675082903611677">"Adicionar ligação"</string>
     <string name="onscreenMergeCallsText" msgid="3692389519611225407">"Juntar chamadas"</string>
     <string name="onscreenSwapCallsText" msgid="2682542150803377991">"Trocar"</string>
     <string name="onscreenManageCallsText" msgid="1162047856081836469">"Gerenciar chamadas"</string>
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 7c176ef..aa6f7f9 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -385,6 +385,7 @@
     private SharedPreferences mTelephonySharedPreferences;
     private PhoneConfigurationManager mPhoneConfigurationManager;
     private final RadioInterfaceCapabilityController mRadioInterfaceCapabilities;
+    private final Telephony2gUpdater mTelephony2gUpdater;
 
     /** User Activity */
     private AtomicBoolean mNotifyUserActivity;
@@ -2401,6 +2402,9 @@
         mRadioInterfaceCapabilities = RadioInterfaceCapabilityController.getInstance();
         mNotifyUserActivity = new AtomicBoolean(false);
         PropertyInvalidatedCache.invalidateCache(TelephonyManager.CACHE_KEY_PHONE_ACCOUNT_TO_SUBID);
+        mTelephony2gUpdater = new Telephony2gUpdater(
+                Executors.newSingleThreadExecutor(), mApp);
+        mTelephony2gUpdater.init();
         publish();
     }
 
diff --git a/src/com/android/phone/Telephony2gUpdater.java b/src/com/android/phone/Telephony2gUpdater.java
new file mode 100644
index 0000000..0919385
--- /dev/null
+++ b/src/com/android/phone/Telephony2gUpdater.java
@@ -0,0 +1,134 @@
+/*
+ * 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 android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.UserManager;
+import android.telephony.RadioAccessFamily;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.RILConstants;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * A {@link BroadcastReceiver} that ensures that user restrictions are correctly applied to
+ * telephony.
+ * This includes handling broadcasts from user restriction state changes, as well as ensuring that
+ * SIM-specific settings are correctly applied when new subscriptions become active.
+ *
+ * Callers are expected to call {@code init()} and keep an instance of this class alive.
+ */
+public class Telephony2gUpdater extends BroadcastReceiver {
+    private static final String TAG = "TelephonyUserManagerReceiver";
+
+    // We can't interact with the HAL on the main thread of the phone process (where
+    // receivers are run by default), so we execute our logic from a separate thread.
+    private final Executor mExecutor;
+    private final Context mContext;
+    private final long mBaseAllowedNetworks;
+
+    public Telephony2gUpdater(Executor executor, Context context) {
+        this(executor, context,
+                RadioAccessFamily.getRafFromNetworkType(RILConstants.PREFERRED_NETWORK_MODE));
+    }
+
+    public Telephony2gUpdater(Executor executor, Context context,
+            long baseAllowedNetworks) {
+        mExecutor = executor;
+        mContext = context;
+        mBaseAllowedNetworks = baseAllowedNetworks;
+    }
+
+    /**
+     * Register the given instance as a {@link BroadcastReceiver} and a {@link
+     * SubscriptionManager.OnSubscriptionsChangedListener}.
+     */
+    public void init() {
+        mContext.getSystemService(SubscriptionManager.class).addOnSubscriptionsChangedListener(
+                mExecutor, new SubscriptionListener());
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
+        mContext.registerReceiver(this, filter);
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (context == null || intent == null) return;
+        Log.i(TAG, "Received callback for action " + intent.getAction());
+        final PendingResult result = goAsync();
+        mExecutor.execute(() -> {
+            Log.i(TAG, "Running handler for action " + intent.getAction());
+            handleUserRestrictionsChanged(context);
+            result.finish();
+        });
+    }
+
+    /**
+     * Update all active subscriptions with allowed network types depending on the current state
+     * of the {@link UserManager.DISALLOW_2G}.
+     */
+    @VisibleForTesting
+    public void handleUserRestrictionsChanged(Context context) {
+        UserManager um = context.getSystemService(UserManager.class);
+        TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+        SubscriptionManager sm = context.getSystemService(SubscriptionManager.class);
+        final long twoGBitmask = TelephonyManager.NETWORK_CLASS_BITMASK_2G;
+
+        boolean shouldDisable2g = um.hasUserRestriction(UserManager.DISALLOW_CELLULAR_2G);
+
+        // This is expected when subscription info cannot be determined. We'll get another
+        // callback in the future from our SubscriptionListener once we have valid subscriptions.
+        List<SubscriptionInfo> subscriptionInfoList = sm.getAvailableSubscriptionInfoList();
+        if (subscriptionInfoList == null) {
+            return;
+        }
+
+        long allowedNetworkTypes = mBaseAllowedNetworks;
+
+        // 2G device admin controls are global
+        for (SubscriptionInfo info : subscriptionInfoList) {
+            TelephonyManager telephonyManager = tm.createForSubscriptionId(
+                    info.getSubscriptionId());
+            if (shouldDisable2g) {
+                allowedNetworkTypes &= ~twoGBitmask;
+            } else {
+                allowedNetworkTypes |= twoGBitmask;
+            }
+            telephonyManager.setAllowedNetworkTypesForReason(
+                    TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS,
+                    allowedNetworkTypes);
+        }
+    }
+
+    private class SubscriptionListener extends SubscriptionManager.OnSubscriptionsChangedListener {
+        @Override
+        public void onSubscriptionsChanged() {
+            Log.i(TAG, "Running handler for subscription change.");
+            handleUserRestrictionsChanged(mContext);
+        }
+    }
+
+}
diff --git a/src/com/android/phone/slicestore/SliceStore.java b/src/com/android/phone/slicestore/SliceStore.java
index 20245f6..bb66973 100644
--- a/src/com/android/phone/slicestore/SliceStore.java
+++ b/src/com/android/phone/slicestore/SliceStore.java
@@ -16,6 +16,7 @@
 
 package com.android.phone.slicestore;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
@@ -27,7 +28,6 @@
 import android.net.ConnectivityManager;
 import android.os.AsyncResult;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.telephony.AnomalyReporter;
@@ -42,6 +42,8 @@
 
 import com.android.internal.telephony.Phone;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Arrays;
@@ -68,6 +70,29 @@
 public class SliceStore extends Handler {
     @NonNull private static final String TAG = "SliceStore";
 
+    /** Unknown failure code. */
+    public static final int FAILURE_CODE_UNKNOWN = 0;
+    /** Network boost purchase failed because the carrier URL is unavailable. */
+    public static final int FAILURE_CODE_CARRIER_URL_UNAVAILABLE = 1;
+    /** Network boost purchase failed because the server is unreachable. */
+    public static final int FAILURE_CODE_SERVER_UNREACHABLE = 2;
+    /** Network boost purchase failed because user authentication failed. */
+    public static final int FAILURE_CODE_AUTHENTICATION_FAILED = 3;
+    /** Network boost purchase failed because the payment failed. */
+    public static final int FAILURE_CODE_PAYMENT_FAILED = 4;
+
+    /**
+     * Failure codes that the carrier website can return when a premium capability purchase fails.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "FAILURE_CODE_" }, value = {
+            FAILURE_CODE_UNKNOWN,
+            FAILURE_CODE_CARRIER_URL_UNAVAILABLE,
+            FAILURE_CODE_SERVER_UNREACHABLE,
+            FAILURE_CODE_AUTHENTICATION_FAILED,
+            FAILURE_CODE_PAYMENT_FAILED})
+    public @interface FailureCode {}
+
     /** Value for an invalid premium capability. */
     public static final int PREMIUM_CAPABILITY_INVALID = -1;
 
@@ -77,8 +102,16 @@
     private static final int EVENT_SLICING_CONFIG_CHANGED = 2;
     /** Display booster notification. */
     private static final int EVENT_DISPLAY_BOOSTER_NOTIFICATION = 3;
-    /** Boost was not purchased within the timeout specified by carrier configs. */
+    /**
+     * Premium capability was not purchased within the timeout specified by
+     * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG}.
+     */
     private static final int EVENT_PURCHASE_TIMEOUT = 4;
+    /**
+     * Network did not set up the slicing configuration within the timeout specified by
+     * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG}.
+     */
+    private static final int EVENT_SETUP_TIMEOUT = 5;
 
     /** UUID to report an anomaly when a premium capability is throttled twice in a row. */
     private static final String UUID_CAPABILITY_THROTTLED_TWICE =
@@ -87,6 +120,13 @@
     private static final String UUID_INVALID_PHONE_ID = "ced79f1a-8ac0-4260-8cf3-08b54c0494f3";
     /** UUID to report an anomaly when receiving an unknown action. */
     private static final String UUID_UNKNOWN_ACTION = "0197efb0-dab1-4b0a-abaf-ac9336ec7923";
+    /** UUID to report an anomaly when receiving an unknown failure code with a non-empty reason. */
+    private static final String UUID_UNKNOWN_FAILURE_CODE = "76943b23-4415-400c-9855-b534fc4fc62c";
+    /**
+     * UUID to report an anomaly when the network fails to set up a slicing configuration after
+     * the user purchases a premium capability.
+     */
+    private static final String UUID_NETWORK_SETUP_FAILED = "12eeffbf-08f8-40ed-9a00-d344199552fc";
 
     /** Action to start the SliceStore application and display the network boost notification. */
     public static final String ACTION_START_SLICE_STORE =
@@ -106,6 +146,9 @@
     /** 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 indicating the purchase request was successful. */
+    private static final String ACTION_SLICE_STORE_RESPONSE_SUCCESS =
+            "com.android.phone.slicestore.action.SLICE_STORE_RESPONSE_SUCCESS";
 
     /** 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";
@@ -114,6 +157,15 @@
     /** 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 duration of the purchased premium capability. */
+    public static final String EXTRA_PURCHASE_DURATION =
+            "com.android.phone.slicestore.extra.PURCHASE_DURATION";
+    /** Extra for the {@link FailureCode} why the premium capability purchase failed. */
+    public static final String EXTRA_FAILURE_CODE =
+            "com.android.phone.slicestore.extra.FAILURE_CODE";
+    /** Extra for the human-readable reason why the premium capability purchase failed. */
+    public static final String EXTRA_FAILURE_REASON =
+            "com.android.phone.slicestore.extra.FAILURE_REASON";
     /**
      * Extra for the application name requesting to purchase the premium capability
      * from the SliceStore application.
@@ -131,6 +183,8 @@
      * 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}.
+     * Sender can modify the intent to specify the failure code and reason for failure with
+     * {@link #EXTRA_FAILURE_CODE} and {@link #EXTRA_FAILURE_REASON}.
      */
     public static final String EXTRA_INTENT_CARRIER_ERROR =
             "com.android.phone.slicestore.extra.INTENT_CARRIER_ERROR";
@@ -150,6 +204,15 @@
      */
     public static final String EXTRA_INTENT_NOT_DEFAULT_DATA =
             "com.android.phone.slicestore.extra.INTENT_NOT_DEFAULT_DATA";
+    /**
+     * Extra for the success PendingIntent that the SliceStore application can send as a response
+     * if the premium capability purchase request was successful.
+     * Sends {@link #ACTION_SLICE_STORE_RESPONSE_SUCCESS}.
+     * Sender can modify the intent to specify a purchase duration with
+     * {@link #EXTRA_PURCHASE_DURATION}.
+     */
+    public static final String EXTRA_INTENT_SUCCESS =
+            "com.android.phone.slicestore.extra.INTENT_SUCCESS";
 
     /** Component name to send an explicit broadcast to SliceStoreBroadcastReceiver. */
     private static final ComponentName SLICE_STORE_COMPONENT_NAME =
@@ -161,8 +224,8 @@
 
     /** The Phone instance used to create the SliceStore */
     @NonNull private final Phone mPhone;
-    /** The set of purchased capabilities. */
-    @NonNull private final Set<Integer> mPurchasedCapabilities = new HashSet<>();
+    /** The set of capabilities that are pending network setup. */
+    @NonNull private final Set<Integer> mPendingSetupCapabilities = new HashSet<>();
     /** The set of throttled capabilities. */
     @NonNull private final Set<Integer> mThrottledCapabilities = new HashSet<>();
     /** A map of pending capabilities to the onComplete message for the purchase request. */
@@ -174,7 +237,7 @@
     @Nullable private NetworkSlicingConfig mSlicingConfig;
 
     private class SliceStoreBroadcastReceiver extends BroadcastReceiver {
-        private final @TelephonyManager.PremiumCapability int mCapability;
+        @TelephonyManager.PremiumCapability private final int mCapability;
 
         SliceStoreBroadcastReceiver(@TelephonyManager.PremiumCapability int capability) {
             mCapability = capability;
@@ -191,11 +254,9 @@
             int capability = intent.getIntExtra(EXTRA_PREMIUM_CAPABILITY,
                     PREMIUM_CAPABILITY_INVALID);
             if (SliceStore.getInstance(phoneId) == null) {
-                String logStr = "SliceStoreBroadcastReceiver( "
+                reportAnomaly(UUID_INVALID_PHONE_ID, "SliceStoreBroadcastReceiver( "
                         + TelephonyManager.convertPremiumCapabilityToString(mCapability)
-                        + ") received invalid phoneId: " + phoneId;
-                loge(logStr);
-                AnomalyReporter.reportAnomaly(UUID.fromString(UUID_INVALID_PHONE_ID), logStr);
+                        + ") received invalid phoneId: " + phoneId);
                 return;
             } else if (capability != mCapability) {
                 logd("SliceStoreBroadcastReceiver("
@@ -214,11 +275,10 @@
                     break;
                 }
                 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);
+                    int failureCode = intent.getIntExtra(EXTRA_FAILURE_CODE, FAILURE_CODE_UNKNOWN);
+                    String failureReason = intent.getStringExtra(EXTRA_FAILURE_REASON);
+                    SliceStore.getInstance(phoneId).onCarrierError(
+                            capability, failureCode, failureReason);
                     break;
                 }
                 case ACTION_SLICE_STORE_RESPONSE_REQUEST_FAILED: {
@@ -238,12 +298,15 @@
                             false);
                     break;
                 }
+                case ACTION_SLICE_STORE_RESPONSE_SUCCESS: {
+                    long duration = intent.getLongExtra(EXTRA_PURCHASE_DURATION, 0);
+                    SliceStore.getInstance(phoneId).onCarrierSuccess(capability, duration);
+                    break;
+                }
                 default:
-                    String logStr = "SliceStoreBroadcastReceiver("
+                    reportAnomaly(UUID_UNKNOWN_ACTION, "SliceStoreBroadcastReceiver("
                             + TelephonyManager.convertPremiumCapabilityToString(mCapability)
-                            + ") received unknown action: " + action;
-                    loge(logStr);
-                    AnomalyReporter.reportAnomaly(UUID.fromString(UUID_UNKNOWN_ACTION), logStr);
+                            + ") received unknown action: " + action);
                     break;
             }
         }
@@ -276,7 +339,7 @@
     }
 
     private SliceStore(@NonNull Phone phone) {
-        super(Looper.myLooper());
+        super(phone.getLooper());
         mPhone = phone;
         // TODO: Create a cached value for slicing config in DataIndication and initialize here
         mPhone.mCi.registerForSlicingConfigChanged(this, EVENT_SLICING_CONFIG_CHANGED, null);
@@ -297,6 +360,7 @@
                 NetworkSlicingConfig config = (NetworkSlicingConfig) ar.result;
                 logd("EVENT_SLICING_CONFIG_CHANGED: from " + mSlicingConfig + " to " + config);
                 mSlicingConfig = config;
+                onSlicingConfigChanged();
                 break;
             }
             case EVENT_DISPLAY_BOOSTER_NOTIFICATION: {
@@ -314,6 +378,12 @@
                 onTimeout(capability);
                 break;
             }
+            case EVENT_SETUP_TIMEOUT:
+                int capability = (int) msg.obj;
+                logd("EVENT_SETUP_TIMEOUT: for capability "
+                        + TelephonyManager.convertPremiumCapabilityToString(capability));
+                onSetupTimeout(capability);
+                break;
             default:
                 loge("Unknown event: " + msg.obj);
         }
@@ -380,13 +450,18 @@
                     onComplete);
             return;
         }
-        if (mPurchasedCapabilities.contains(capability) || isSlicingConfigActive(capability)) {
-            // TODO (b/245882601): Handle capability expiry
+        if (isSlicingConfigActive(capability)) {
             sendPurchaseResult(capability,
                     TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED,
                     onComplete);
             return;
         }
+        if (mPendingSetupCapabilities.contains(capability)) {
+            sendPurchaseResult(capability,
+                    TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP,
+                    onComplete);
+            return;
+        }
         if (mThrottledCapabilities.contains(capability)) {
             sendPurchaseResult(capability,
                     TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED,
@@ -456,10 +531,20 @@
                         throttleDuration);
             }
         } else {
-            String logStr = TelephonyManager.convertPremiumCapabilityToString(capability)
-                    + " is already throttled.";
-            loge(logStr);
-            AnomalyReporter.reportAnomaly(UUID.fromString(UUID_CAPABILITY_THROTTLED_TWICE), logStr);
+            reportAnomaly(UUID_CAPABILITY_THROTTLED_TWICE,
+                    TelephonyManager.convertPremiumCapabilityToString(capability)
+                            + " is already throttled.");
+        }
+    }
+
+    private void onSlicingConfigChanged() {
+        for (int capability : new int[] {TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY}) {
+            if (isSlicingConfigActive(capability) && hasMessages(EVENT_SETUP_TIMEOUT, capability)) {
+                logd("Successfully set up slicing configuration for "
+                        + TelephonyManager.convertPremiumCapabilityToString(capability));
+                mPendingSetupCapabilities.remove(capability);
+                removeMessages(EVENT_SETUP_TIMEOUT, capability);
+            }
         }
     }
 
@@ -481,13 +566,16 @@
         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));
+                createPendingIntent(ACTION_SLICE_STORE_RESPONSE_CANCELED, capability, false));
         intent.putExtra(EXTRA_INTENT_CARRIER_ERROR,
-                createPendingIntent(ACTION_SLICE_STORE_RESPONSE_CARRIER_ERROR, capability));
+                createPendingIntent(ACTION_SLICE_STORE_RESPONSE_CARRIER_ERROR, capability, true));
         intent.putExtra(EXTRA_INTENT_REQUEST_FAILED,
-                createPendingIntent(ACTION_SLICE_STORE_RESPONSE_REQUEST_FAILED, capability));
+                createPendingIntent(ACTION_SLICE_STORE_RESPONSE_REQUEST_FAILED, capability, false));
         intent.putExtra(EXTRA_INTENT_NOT_DEFAULT_DATA,
-                createPendingIntent(ACTION_SLICE_STORE_RESPONSE_NOT_DEFAULT_DATA, capability));
+                createPendingIntent(ACTION_SLICE_STORE_RESPONSE_NOT_DEFAULT_DATA, capability,
+                        false));
+        intent.putExtra(EXTRA_INTENT_SUCCESS,
+                createPendingIntent(ACTION_SLICE_STORE_RESPONSE_SUCCESS, capability, true));
         logd("Broadcasting start intent to SliceStoreBroadcastReceiver.");
         mPhone.getContext().sendBroadcast(intent);
 
@@ -498,6 +586,7 @@
         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);
+        filter.addAction(ACTION_SLICE_STORE_RESPONSE_SUCCESS);
         mPhone.getContext().registerReceiver(mSliceStoreBroadcastReceivers.get(capability), filter);
     }
 
@@ -506,15 +595,18 @@
      *
      * @param action The action that will be sent for this PendingIntent
      * @param capability The premium capability that was requested.
+     * @param mutable {@code true} if the PendingIntent should be mutable and
+     *                {@code false} if it should be immutable.
      * @return The PendingIntent for the given action and capability.
      */
     @NonNull private PendingIntent createPendingIntent(@NonNull String action,
-            @TelephonyManager.PremiumCapability int capability) {
+            @TelephonyManager.PremiumCapability int capability, boolean mutable) {
         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);
+        return PendingIntent.getBroadcast(mPhone.getContext(), capability, intent,
+                PendingIntent.FLAG_CANCEL_CURRENT
+                        | (mutable ? PendingIntent.FLAG_MUTABLE : PendingIntent.FLAG_IMMUTABLE));
     }
 
     private void onTimeout(@TelephonyManager.PremiumCapability int capability) {
@@ -531,9 +623,43 @@
                 capability, TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT, true);
     }
 
-    private void onCarrierSuccess(@TelephonyManager.PremiumCapability int capability) {
-        // TODO(b/245882601): Process and return success.
-        //  Probably need to handle capability expiry as well
+    private void onCarrierError(@TelephonyManager.PremiumCapability int capability,
+            @FailureCode int failureCode, @Nullable String failureReason) {
+        logd("Carrier error for capability: "
+                + TelephonyManager.convertPremiumCapabilityToString(capability) + " with code: "
+                + convertFailureCodeToString(failureCode) + " and reason: " + failureReason);
+        if (failureCode == FAILURE_CODE_UNKNOWN && !TextUtils.isEmpty(failureReason)) {
+            reportAnomaly(UUID_UNKNOWN_FAILURE_CODE,
+                    "Failure code needs to be added for: " + failureReason);
+        }
+        sendPurchaseResultFromSliceStore(capability,
+                TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR, true);
+    }
+
+    private void onCarrierSuccess(@TelephonyManager.PremiumCapability int capability,
+            long duration) {
+        logd("Successfully purchased premium capability "
+                + TelephonyManager.convertPremiumCapabilityToString(capability)
+                + " for " + TimeUnit.MILLISECONDS.toMinutes(duration) + " minutes.");
+        mPendingSetupCapabilities.add(capability);
+        long setupDuration = getCarrierConfigs().getLong(
+                CarrierConfigManager.KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG);
+        logd("Waiting " + TimeUnit.MILLISECONDS.toMinutes(setupDuration) + " minutes for the "
+                + "network to set up the slicing configuration.");
+        sendMessageDelayed(obtainMessage(EVENT_SETUP_TIMEOUT, capability), setupDuration);
+        sendPurchaseResultFromSliceStore(
+                capability, TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS, false);
+    }
+
+    private void onSetupTimeout(@TelephonyManager.PremiumCapability int capability) {
+        logd("onSetupTimeout: " + TelephonyManager.convertPremiumCapabilityToString(capability));
+        mPendingSetupCapabilities.remove(capability);
+        if (!isSlicingConfigActive(capability)) {
+            reportAnomaly(UUID_NETWORK_SETUP_FAILED,
+                    "Failed to set up slicing configuration for capability "
+                            + TelephonyManager.convertPremiumCapabilityToString(capability)
+                            + " within the time specified.");
+        }
     }
 
     @Nullable private PersistableBundle getCarrierConfigs() {
@@ -628,6 +754,29 @@
         return true;
     }
 
+    /**
+     * Returns the failure code {@link FailureCode} as a String.
+     *
+     * @param failureCode The failure code.
+     * @return The failure code as a String.
+     */
+    @NonNull public static String convertFailureCodeToString(@FailureCode int failureCode) {
+        switch (failureCode) {
+            case FAILURE_CODE_UNKNOWN: return "UNKNOWN";
+            case FAILURE_CODE_CARRIER_URL_UNAVAILABLE: return "CARRIER_URL_UNAVAILABLE";
+            case FAILURE_CODE_SERVER_UNREACHABLE: return "SERVER_UNREACHABLE";
+            case FAILURE_CODE_AUTHENTICATION_FAILED: return "AUTHENTICATION_FAILED";
+            case FAILURE_CODE_PAYMENT_FAILED: return "PAYMENT_FAILED";
+            default:
+                return "UNKNOWN(" + failureCode + ")";
+        }
+    }
+
+    private void reportAnomaly(@NonNull String uuid, @NonNull String log) {
+        loge(log);
+        AnomalyReporter.reportAnomaly(UUID.fromString(uuid), log);
+    }
+
     private void logd(String s) {
         Log.d(TAG + "-" + mPhone.getPhoneId(), s);
     }
diff --git a/tests/src/com/android/TestContext.java b/tests/src/com/android/TestContext.java
index 7edbed9..7c3a842 100644
--- a/tests/src/com/android/TestContext.java
+++ b/tests/src/com/android/TestContext.java
@@ -31,6 +31,7 @@
 import android.os.Looper;
 import android.os.PersistableBundle;
 import android.os.Process;
+import android.os.UserManager;
 import android.telecom.TelecomManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
@@ -58,6 +59,7 @@
     @Mock TelephonyManager mMockTelephonyManager;
     @Mock SubscriptionManager mMockSubscriptionManager;
     @Mock ImsManager mMockImsManager;
+    @Mock UserManager mMockUserManager;
 
     private SparseArray<PersistableBundle> mCarrierConfigs = new SparseArray<>();
 
@@ -147,6 +149,9 @@
             case(Context.TELEPHONY_IMS_SERVICE) : {
                 return mMockImsManager;
             }
+            case(Context.USER_SERVICE) : {
+                return mMockUserManager;
+            }
         }
         return null;
     }
@@ -165,6 +170,9 @@
         if (serviceClass == SubscriptionManager.class) {
             return Context.TELEPHONY_SUBSCRIPTION_SERVICE;
         }
+        if (serviceClass == UserManager.class) {
+            return Context.USER_SERVICE;
+        }
         return null;
     }
 
diff --git a/tests/src/com/android/phone/Telephony2gUpdaterTest.java b/tests/src/com/android/phone/Telephony2gUpdaterTest.java
new file mode 100644
index 0000000..3443767
--- /dev/null
+++ b/tests/src/com/android/phone/Telephony2gUpdaterTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.UserManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+@RunWith(AndroidJUnit4.class)
+public class Telephony2gUpdaterTest extends TelephonyTestBase {
+    private Telephony2gUpdater mTelephony2gUpdater;
+    private Executor mExecutor;
+
+    private UserManager mMockUserManager;
+    private TelephonyManager mMockTelephonyManager;
+    private SubscriptionManager mMockSubscriptionManager;
+
+    // 2G Bitmask is 0b10000000_01001011
+    private static final long BASE_NETWORK = 0b11111111_11111111;
+    private static final long EXPECTED_DISABLED = 0b01111111_10110100;
+    private static final long EXPECTED_ENABLED = 0b11111111_11111111;
+
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mMockTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+        mMockUserManager = mContext.getSystemService(UserManager.class);
+        mMockSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
+
+        mExecutor = Executors.newSingleThreadExecutor();
+        mTelephony2gUpdater = new Telephony2gUpdater(mExecutor,
+                getTestContext(), BASE_NETWORK);
+    }
+
+    @Test
+    public void handleUserRestrictionsChanged_noSubscriptions_noAllowedNetworksChanged() {
+        when(mMockSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
+                new ArrayList<>());
+        mTelephony2gUpdater.handleUserRestrictionsChanged(getTestContext());
+        verify(mMockTelephonyManager, never()).setAllowedNetworkTypesForReason(anyInt(), anyInt());
+    }
+
+    @Test
+    public void handleUserRestrictionsChanged_nullSubscriptions_noAllowedNetworksChanged() {
+        when(mMockSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(null);
+        mTelephony2gUpdater.handleUserRestrictionsChanged(getTestContext());
+        verify(mMockTelephonyManager, never()).setAllowedNetworkTypesForReason(anyInt(), anyInt());
+    }
+
+    @Test
+    public void handleUserRestrictionsChanged_oneSubscription_allowedNetworksUpdated() {
+        TelephonyManager tmSubscription1 = mock(TelephonyManager.class);
+        when(mMockSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
+                Collections.singletonList(getSubInfo(1)));
+        when(mMockTelephonyManager.createForSubscriptionId(1)).thenReturn(tmSubscription1);
+        when(mMockUserManager.hasUserRestriction(UserManager.DISALLOW_CELLULAR_2G)).thenReturn(
+                true);
+
+        mTelephony2gUpdater.handleUserRestrictionsChanged(getTestContext());
+
+        System.out.println(TelephonyManager.convertNetworkTypeBitmaskToString(11L));
+        verify(tmSubscription1, times(1)).setAllowedNetworkTypesForReason(
+                TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS, EXPECTED_DISABLED);
+    }
+
+    @Test
+    public void handleUserRestrictionsChanged_manySubscriptionsDisallow2g_allowedNetworkUpdated() {
+
+        // Two subscriptions are available
+        when(mMockSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
+                Arrays.asList(getSubInfo(1), getSubInfo(2)));
+        TelephonyManager tmSubscription1 = mock(TelephonyManager.class);
+        TelephonyManager tmSubscription2 = mock(TelephonyManager.class);
+        when(mMockTelephonyManager.createForSubscriptionId(1)).thenReturn(tmSubscription1);
+        when(mMockTelephonyManager.createForSubscriptionId(2)).thenReturn(tmSubscription2);
+        // 2g is disallowed
+        when(mMockUserManager.hasUserRestriction(UserManager.DISALLOW_CELLULAR_2G)).thenReturn(
+                true);
+
+        mTelephony2gUpdater.handleUserRestrictionsChanged(getTestContext());
+
+        verify(tmSubscription1, times(1)).setAllowedNetworkTypesForReason(
+                TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS, EXPECTED_DISABLED);
+        verify(tmSubscription1, times(1)).setAllowedNetworkTypesForReason(
+                TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS, EXPECTED_DISABLED);
+    }
+
+    @Test
+    public void handleUserRestrictionsChanged_manySubscriptionsAllow2g_allowedNetworkUpdated() {
+
+        // Two subscriptions are available
+        when(mMockSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
+                Arrays.asList(getSubInfo(1), getSubInfo(2)));
+        TelephonyManager tmSubscription1 = mock(TelephonyManager.class);
+        TelephonyManager tmSubscription2 = mock(TelephonyManager.class);
+        when(mMockTelephonyManager.createForSubscriptionId(1)).thenReturn(tmSubscription1);
+        when(mMockTelephonyManager.createForSubscriptionId(2)).thenReturn(tmSubscription2);
+
+        // 2g is allowed
+        when(mMockUserManager.hasUserRestriction(UserManager.DISALLOW_CELLULAR_2G)).thenReturn(
+                false);
+
+        mTelephony2gUpdater.handleUserRestrictionsChanged(getTestContext());
+
+        verify(tmSubscription1, times(1)).setAllowedNetworkTypesForReason(
+                TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS, EXPECTED_ENABLED);
+        verify(tmSubscription1, times(1)).setAllowedNetworkTypesForReason(
+                TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS, EXPECTED_ENABLED);
+    }
+
+    private SubscriptionInfo getSubInfo(int id) {
+        return new SubscriptionInfo(id, "890126042XXXXXXXXXXX", 0, "T-mobile", "T-mobile", 0, 255,
+                "12345", 0, null, "310", "260", "156", false, null, null);
+    }
+}