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);
+ }
+}