Merge "Add support for daily and monthly notification maximums"
diff --git a/src/com/android/phone/slice/SlicePurchaseController.java b/src/com/android/phone/slice/SlicePurchaseController.java
index ead6b8c..f258e2c 100644
--- a/src/com/android/phone/slice/SlicePurchaseController.java
+++ b/src/com/android/phone/slice/SlicePurchaseController.java
@@ -31,6 +31,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.os.AsyncResult;
import android.os.Handler;
@@ -57,6 +58,9 @@
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -115,8 +119,8 @@
private static final int EVENT_PURCHASE_UNTHROTTLED = 1;
/** Slicing config changed. */
private static final int EVENT_SLICING_CONFIG_CHANGED = 2;
- /** Display booster notification. */
- private static final int EVENT_DISPLAY_BOOSTER_NOTIFICATION = 3;
+ /** Start slice purchase application. */
+ private static final int EVENT_START_SLICE_PURCHASE_APP = 3;
/**
* Premium capability was not purchased within the timeout specified by
* {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG}.
@@ -170,6 +174,9 @@
/** Action indicating the purchase request was successful. */
private static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS =
"com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_SUCCESS";
+ /** Action indicating the slice purchase application showed the network boost notification. */
+ private static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN =
+ "com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN";
/** Extra for the phone index to send to the slice purchase application. */
public static final String EXTRA_PHONE_ID = "com.android.phone.slice.extra.PHONE_ID";
@@ -235,12 +242,35 @@
*/
public static final String EXTRA_INTENT_SUCCESS =
"com.android.phone.slice.extra.INTENT_SUCCESS";
+ /**
+ * Extra for the PendingIntent that the slice purchase application can send to indicate
+ * that it displayed the network boost notification to the user.
+ * Sends {@link #ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN}.
+ */
+ public static final String EXTRA_INTENT_NOTIFICATION_SHOWN =
+ "com.android.phone.slice.extra.NOTIFICATION_SHOWN";
- /** Component name to send an explicit broadcast to SlicePurchaseBroadcastReceiver. */
+ /** Component name for the SlicePurchaseBroadcastReceiver. */
private static final ComponentName SLICE_PURCHASE_APP_COMPONENT_NAME =
ComponentName.unflattenFromString(
"com.android.carrierdefaultapp/.SlicePurchaseBroadcastReceiver");
+ /** Shared preference name for network boost notification preferences. */
+ private static final String NETWORK_BOOST_NOTIFICATION_PREFERENCES =
+ "network_boost_notification_preferences";
+ /** Shared preference key for daily count of network boost notifications. */
+ private static final String KEY_DAILY_NOTIFICATION_COUNT = "daily_notification_count";
+ /** Shared preference key for monthly count of network boost notifications. */
+ private static final String KEY_MONTHLY_NOTIFICATION_COUNT = "monthly_notification_count";
+ /**
+ * Shared preference key for the date the daily or monthly counts of network boost notifications
+ * were last reset.
+ * A String with ISO-8601 format {@code YYYY-MM-DD}, from {@link LocalDate#toString}.
+ * For example, if the count was last updated on December 25, 2020, this would be `2020-12-25`.
+ */
+ private static final String KEY_NOTIFICATION_COUNT_LAST_RESET_DATE =
+ "notification_count_last_reset_date";
+
/** Map of phone ID -> SlicePurchaseController instances. */
@NonNull private static final Map<Integer, SlicePurchaseController> sInstances =
new HashMap<>();
@@ -261,21 +291,37 @@
mSlicePurchaseControllerBroadcastReceivers = new HashMap<>();
/** The current network slicing configuration. */
@Nullable private NetworkSlicingConfig mSlicingConfig;
- /** Premium network entitlement query API */
+ /** Premium network entitlement query API. */
@NonNull private final PremiumNetworkEntitlementApi mPremiumNetworkEntitlementApi;
+ /** LocalDate to use when resetting notification counts. {@code null} except when testing. */
+ @Nullable private LocalDate mLocalDate;
+ /** The number of times the network boost notification has been shown today. */
+ private int mDailyCount;
+ /** The number of times the network boost notification has been shown this month. */
+ private int mMonthlyCount;
/**
* BroadcastReceiver to receive responses from the slice purchase application.
*/
- @VisibleForTesting
- public class SlicePurchaseControllerBroadcastReceiver extends BroadcastReceiver {
+ private class SlicePurchaseControllerBroadcastReceiver extends BroadcastReceiver {
@TelephonyManager.PremiumCapability private final int mCapability;
+ /**
+ * Create a SlicePurchaseControllerBroadcastReceiver for the given capability
+ *
+ * @param capability The requested capability to listen to response for.
+ */
SlicePurchaseControllerBroadcastReceiver(
@TelephonyManager.PremiumCapability int capability) {
mCapability = capability;
}
+ /**
+ * Process responses from the slice purchase application.
+ *
+ * @param context The Context in which the receiver is running.
+ * @param intent The Intent being received.
+ */
@Override
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
String action = intent.getAction();
@@ -340,6 +386,10 @@
capability, duration);
break;
}
+ case ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN: {
+ SlicePurchaseController.getInstance(phoneId).onNotificationShown();
+ break;
+ }
default:
reportAnomaly(UUID_UNKNOWN_ACTION, "SlicePurchaseControllerBroadcastReceiver("
+ TelephonyManager.convertPremiumCapabilityToString(mCapability)
@@ -381,6 +431,7 @@
/**
* Create a SlicePurchaseController for the given phone on the given looper.
+ *
* @param phone The Phone to create the SlicePurchaseController for.
* @param looper The Looper to run the SlicePurchaseController on.
*/
@@ -390,8 +441,19 @@
mPhone = phone;
// TODO: Create a cached value for slicing config in DataIndication and initialize here
mPhone.mCi.registerForSlicingConfigChanged(this, EVENT_SLICING_CONFIG_CHANGED, null);
- mPremiumNetworkEntitlementApi = new PremiumNetworkEntitlementApi(mPhone,
- getCarrierConfigs());
+ mPremiumNetworkEntitlementApi =
+ new PremiumNetworkEntitlementApi(mPhone, getCarrierConfigs());
+ updateNotificationCounts();
+ }
+
+ /**
+ * Set the LocalDate to use for resetting daily and monthly notification counts.
+ *
+ * @param localDate The LocalDate instance to use.
+ */
+ @VisibleForTesting
+ public void setLocalDate(@NonNull LocalDate localDate) {
+ mLocalDate = localDate;
}
@Override
@@ -412,12 +474,12 @@
onSlicingConfigChanged();
break;
}
- case EVENT_DISPLAY_BOOSTER_NOTIFICATION: {
+ case EVENT_START_SLICE_PURCHASE_APP: {
int capability = msg.arg1;
String appName = (String) msg.obj;
- logd("EVENT_DISPLAY_BOOSTER_NOTIFICATION: " + appName + " requests capability "
+ logd("EVENT_START_SLICE_PURCHASE_APP: " + appName + " requests capability "
+ TelephonyManager.convertPremiumCapabilityToString(capability));
- onDisplayBoosterNotification(capability, appName);
+ onStartSlicePurchaseApplication(capability, appName);
break;
}
case EVENT_PURCHASE_TIMEOUT: {
@@ -531,10 +593,10 @@
return;
}
- // All state checks passed. Mark purchase pending and display the booster notification to
- // prompt user purchase. Process through the handler since this method is synchronized.
+ // All state checks passed. Mark purchase pending and start the slice purchase application.
+ // Process through the handler since this method is synchronized.
mPendingPurchaseCapabilities.put(capability, onComplete);
- sendMessage(obtainMessage(EVENT_DISPLAY_BOOSTER_NOTIFICATION, capability, 0 /* unused */,
+ sendMessage(obtainMessage(EVENT_START_SLICE_PURCHASE_APP, capability, 0 /* unused */,
appName));
}
@@ -594,7 +656,7 @@
}
}
- private void onDisplayBoosterNotification(@TelephonyManager.PremiumCapability int capability,
+ private void onStartSlicePurchaseApplication(@TelephonyManager.PremiumCapability int capability,
@NonNull String appName) {
PremiumNetworkEntitlementResponse premiumNetworkEntitlementResponse =
mPremiumNetworkEntitlementApi.checkEntitlementStatus(capability);
@@ -627,6 +689,17 @@
return;
}
+ updateNotificationCounts();
+ if (mMonthlyCount >= getCarrierConfigs().getInt(
+ CarrierConfigManager.KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT)
+ || mDailyCount >= getCarrierConfigs().getInt(
+ CarrierConfigManager.KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT)) {
+ logd("Reached maximum number of network boost notifications.");
+ handlePurchaseResult(capability,
+ TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, false);
+ return;
+ }
+
// Start timeout for purchase completion.
long timeout = getCarrierConfigs().getLong(CarrierConfigManager
.KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG);
@@ -653,6 +726,8 @@
false));
intent.putExtra(EXTRA_INTENT_SUCCESS, createPendingIntent(
ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS, capability, true));
+ intent.putExtra(EXTRA_INTENT_NOTIFICATION_SHOWN, createPendingIntent(
+ ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN, capability, false));
logd("Broadcasting start intent to SlicePurchaseBroadcastReceiver.");
mPhone.getContext().sendBroadcast(intent);
@@ -665,6 +740,7 @@
filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_REQUEST_FAILED);
filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUBSCRIPTION);
filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS);
+ filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN);
mPhone.getContext().registerReceiver(
mSlicePurchaseControllerBroadcastReceivers.get(capability), filter);
}
@@ -742,6 +818,73 @@
}
}
+ private void onNotificationShown() {
+ SharedPreferences sp =
+ mPhone.getContext().getSharedPreferences(NETWORK_BOOST_NOTIFICATION_PREFERENCES, 0);
+ mDailyCount = sp.getInt((KEY_DAILY_NOTIFICATION_COUNT + mPhone.getPhoneId()), 0) + 1;
+ mMonthlyCount = sp.getInt((KEY_MONTHLY_NOTIFICATION_COUNT + mPhone.getPhoneId()), 0) + 1;
+ logd("Network boost notification was shown " + mDailyCount + " times today and "
+ + mMonthlyCount + " times this month.");
+
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putInt((KEY_DAILY_NOTIFICATION_COUNT + mPhone.getPhoneId()), mDailyCount);
+ editor.putInt((KEY_MONTHLY_NOTIFICATION_COUNT + mPhone.getPhoneId()), mMonthlyCount);
+ editor.apply();
+
+ // Don't call updateNotificationCounts here because it will be called whenever a new
+ // purchase request comes in or when SlicePurchaseController is initialized.
+ }
+
+ /**
+ * Update the current daily and monthly network boost notification counts.
+ * If it has been at least a day since the last daily reset or at least a month since the last
+ * monthly reset, reset the current daily or monthly notification counts.
+ */
+ @VisibleForTesting
+ public void updateNotificationCounts() {
+ SharedPreferences sp =
+ mPhone.getContext().getSharedPreferences(NETWORK_BOOST_NOTIFICATION_PREFERENCES, 0);
+ mDailyCount = sp.getInt((KEY_DAILY_NOTIFICATION_COUNT + mPhone.getPhoneId()), 0);
+ mMonthlyCount = sp.getInt((KEY_MONTHLY_NOTIFICATION_COUNT + mPhone.getPhoneId()), 0);
+
+ if (mLocalDate == null) {
+ // Standardize to UTC to prevent default time zone dependency
+ mLocalDate = LocalDate.now(ZoneId.of("UTC"));
+ }
+ LocalDate lastLocalDate = LocalDate.of(1, 1, 1);
+ String lastLocalDateString = sp.getString(
+ (KEY_NOTIFICATION_COUNT_LAST_RESET_DATE + mPhone.getPhoneId()), "");
+ if (!TextUtils.isEmpty(lastLocalDateString)) {
+ try {
+ lastLocalDate = LocalDate.parse(lastLocalDateString);
+ } catch (DateTimeParseException e) {
+ loge("Error parsing LocalDate from SharedPreferences: " + e);
+ }
+ }
+ logd("updateNotificationCounts: mDailyCount=" + mDailyCount + ", mMonthlyCount="
+ + mMonthlyCount + ", mLocalDate=" + mLocalDate + ", lastLocalDate="
+ + lastLocalDate);
+
+ boolean resetMonthly = lastLocalDate.getYear() != mLocalDate.getYear()
+ || lastLocalDate.getMonthValue() != mLocalDate.getMonthValue();
+ boolean resetDaily = resetMonthly
+ || lastLocalDate.getDayOfMonth() != mLocalDate.getDayOfMonth();
+ if (resetDaily) {
+ logd("Resetting daily" + (resetMonthly ? " and monthly" : "") + " notification count.");
+ SharedPreferences.Editor editor = sp.edit();
+ if (resetMonthly) {
+ mMonthlyCount = 0;
+ editor.putInt((KEY_MONTHLY_NOTIFICATION_COUNT + mPhone.getPhoneId()),
+ mMonthlyCount);
+ }
+ mDailyCount = 0;
+ editor.putInt((KEY_DAILY_NOTIFICATION_COUNT + mPhone.getPhoneId()), mDailyCount);
+ editor.putString((KEY_NOTIFICATION_COUNT_LAST_RESET_DATE + mPhone.getPhoneId()),
+ mLocalDate.toString());
+ editor.apply();
+ }
+ }
+
@Nullable private PersistableBundle getCarrierConfigs() {
return mPhone.getContext().getSystemService(CarrierConfigManager.class)
.getConfigForSubId(mPhone.getSubId());
diff --git a/tests/src/com/android/TestContext.java b/tests/src/com/android/TestContext.java
index 7c3a842..720d235 100644
--- a/tests/src/com/android/TestContext.java
+++ b/tests/src/com/android/TestContext.java
@@ -61,7 +61,11 @@
@Mock ImsManager mMockImsManager;
@Mock UserManager mMockUserManager;
- private SparseArray<PersistableBundle> mCarrierConfigs = new SparseArray<>();
+ private final SparseArray<PersistableBundle> mCarrierConfigs = new SparseArray<>();
+
+ private Intent mIntent;
+
+ private BroadcastReceiver mReceiver;
private final HashSet<String> mPermissionTable = new HashSet<>();
@@ -105,28 +109,42 @@
}
@Override
+ public void sendBroadcast(Intent intent) {
+ mIntent = intent;
+ }
+
+ @Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ mReceiver = receiver;
return null;
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {
+ mReceiver = receiver;
return null;
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
+ mReceiver = receiver;
return null;
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler, int flags) {
+ mReceiver = receiver;
return null;
}
@Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ mReceiver = null;
+ }
+
+ @Override
public ContentResolver getContentResolver() {
return null;
}
@@ -134,22 +152,22 @@
@Override
public Object getSystemService(String name) {
switch (name) {
- case (Context.CARRIER_CONFIG_SERVICE) : {
+ case Context.CARRIER_CONFIG_SERVICE: {
return mMockCarrierConfigManager;
}
- case (Context.TELECOM_SERVICE) : {
+ case Context.TELECOM_SERVICE: {
return mMockTelecomManager;
}
- case (Context.TELEPHONY_SERVICE) : {
+ case Context.TELEPHONY_SERVICE: {
return mMockTelephonyManager;
}
- case (Context.TELEPHONY_SUBSCRIPTION_SERVICE) : {
+ case Context.TELEPHONY_SUBSCRIPTION_SERVICE: {
return mMockSubscriptionManager;
}
- case(Context.TELEPHONY_IMS_SERVICE) : {
+ case Context.TELEPHONY_IMS_SERVICE: {
return mMockImsManager;
}
- case(Context.USER_SERVICE) : {
+ case Context.USER_SERVICE: {
return mMockUserManager;
}
}
@@ -170,6 +188,9 @@
if (serviceClass == SubscriptionManager.class) {
return Context.TELEPHONY_SUBSCRIPTION_SERVICE;
}
+ if (serviceClass == ImsManager.class) {
+ return Context.TELEPHONY_IMS_SERVICE;
+ }
if (serviceClass == UserManager.class) {
return Context.USER_SERVICE;
}
@@ -252,6 +273,14 @@
}
}
+ public Intent getBroadcast() {
+ return mIntent;
+ }
+
+ public BroadcastReceiver getBroadcastReceiver() {
+ return mReceiver;
+ }
+
private static void logd(String s) {
Log.d(TAG, s);
}
diff --git a/tests/src/com/android/phone/SlicePurchaseControllerTest.java b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
similarity index 75%
rename from tests/src/com/android/phone/SlicePurchaseControllerTest.java
rename to tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
index ebcf15d..e9e23f3 100644
--- a/tests/src/com/android/phone/SlicePurchaseControllerTest.java
+++ b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.phone;
+package com.android.phone.slice;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -26,15 +26,19 @@
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.HandlerThread;
@@ -53,38 +57,42 @@
import com.android.TelephonyTestBase;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.Phone;
-import com.android.phone.slice.PremiumNetworkEntitlementApi;
-import com.android.phone.slice.PremiumNetworkEntitlementResponse;
-import com.android.phone.slice.SlicePurchaseController;
-import com.android.phone.slice.SlicePurchaseController.SlicePurchaseControllerBroadcastReceiver;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
+import java.time.LocalDate;
import java.util.Collections;
import java.util.Map;
@RunWith(AndroidJUnit4.class)
public class SlicePurchaseControllerTest extends TelephonyTestBase {
private static final String TAG = "SlicePurchaseControllerTest";
+ private static final String DAILY_NOTIFICATION_COUNT_KEY = "daily_notification_count0";
+ private static final String MONTHLY_NOTIFICATION_COUNT_KEY = "monthly_notification_count0";
+ private static final int YEAR = 2000;
+ private static final int MONTH = 6;
+ private static final int DATE = 1;
private static final int PHONE_ID = 0;
+ private static final int DAILY_NOTIFICATION_MAX = 3;
+ private static final int MONTHLY_NOTIFICATION_MAX = 5;
private static final long NOTIFICATION_TIMEOUT = 1000;
private static final long PURCHASE_CONDITION_TIMEOUT = 2000;
private static final long NETWORK_SETUP_TIMEOUT = 3000;
private static final long THROTTLE_TIMEOUT = 4000;
@Mock Phone mPhone;
- @Mock Context mMockedContext;
@Mock CarrierConfigManager mCarrierConfigManager;
@Mock CommandsInterface mCommandsInterface;
@Mock ServiceState mServiceState;
@Mock PremiumNetworkEntitlementApi mPremiumNetworkEntitlementApi;
+ @Mock SharedPreferences mSharedPreferences;
+ @Mock SharedPreferences.Editor mEditor;
private SlicePurchaseController mSlicePurchaseController;
- private SlicePurchaseControllerBroadcastReceiver mBroadcastReceiver;
private PersistableBundle mBundle;
private PremiumNetworkEntitlementResponse mEntitlementResponse;
private Handler mHandler;
@@ -106,17 +114,34 @@
mTestableLooper = new TestableLooper(mHandler.getLooper());
doReturn(PHONE_ID).when(mPhone).getPhoneId();
- doReturn(mMockedContext).when(mPhone).getContext();
+ doReturn(mContext).when(mPhone).getContext();
doReturn(mServiceState).when(mPhone).getServiceState();
mPhone.mCi = mCommandsInterface;
- doReturn(Context.CARRIER_CONFIG_SERVICE).when(mMockedContext)
- .getSystemServiceName(eq(CarrierConfigManager.class));
- doReturn(mCarrierConfigManager).when(mMockedContext)
- .getSystemService(eq(Context.CARRIER_CONFIG_SERVICE));
+ doReturn(mCarrierConfigManager).when(mContext)
+ .getSystemService(Context.CARRIER_CONFIG_SERVICE);
mBundle = new PersistableBundle();
+ mBundle.putInt(
+ CarrierConfigManager.KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT,
+ DAILY_NOTIFICATION_MAX);
+ mBundle.putInt(
+ CarrierConfigManager.KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT,
+ MONTHLY_NOTIFICATION_MAX);
doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+ doReturn(mSharedPreferences).when(mContext).getSharedPreferences(anyString(), anyInt());
+ doReturn(mEditor).when(mSharedPreferences).edit();
+ doAnswer(invocation -> {
+ doReturn(invocation.getArgument(1)).when(mSharedPreferences)
+ .getInt(eq(invocation.getArgument(0)), anyInt());
+ return null;
+ }).when(mEditor).putInt(anyString(), anyInt());
+ doAnswer(invocation -> {
+ doReturn(invocation.getArgument(1)).when(mSharedPreferences)
+ .getString(eq(invocation.getArgument(0)), anyString());
+ return null;
+ }).when(mEditor).putString(anyString(), anyString());
+
// create a spy to mock final PendingIntent methods
SlicePurchaseController slicePurchaseController =
new SlicePurchaseController(mPhone, mHandler.getLooper());
@@ -159,7 +184,6 @@
new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY});
mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING,
SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mPhone).getSubId();
// retry to verify available
@@ -188,19 +212,65 @@
};
for (String url : invalidUrls) {
mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING, url);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
assertFalse(mSlicePurchaseController.isPremiumCapabilityAvailableForPurchase(
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY));
}
mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING,
SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
assertTrue(mSlicePurchaseController.isPremiumCapabilityAvailableForPurchase(
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY));
}
@Test
+ public void testUpdateNotificationCounts() {
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR, MONTH, DATE));
+ mSlicePurchaseController.updateNotificationCounts();
+
+ // change only date, month and year remain the same
+ Mockito.clearInvocations(mEditor);
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR, MONTH, DATE + 1));
+ mSlicePurchaseController.updateNotificationCounts();
+ verify(mEditor).putInt(eq(DAILY_NOTIFICATION_COUNT_KEY), eq(0));
+ verify(mEditor, never()).putInt(eq(MONTHLY_NOTIFICATION_COUNT_KEY), eq(0));
+
+ // change only month, date and year remain the same
+ Mockito.clearInvocations(mEditor);
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR, MONTH + 1, DATE + 1));
+ mSlicePurchaseController.updateNotificationCounts();
+ verify(mEditor).putInt(eq(DAILY_NOTIFICATION_COUNT_KEY), eq(0));
+ verify(mEditor).putInt(eq(MONTHLY_NOTIFICATION_COUNT_KEY), eq(0));
+
+ // change only year, date and month remain the same
+ Mockito.clearInvocations(mEditor);
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR + 1, MONTH + 1, DATE + 1));
+ mSlicePurchaseController.updateNotificationCounts();
+ verify(mEditor).putInt(eq(DAILY_NOTIFICATION_COUNT_KEY), eq(0));
+ verify(mEditor).putInt(eq(MONTHLY_NOTIFICATION_COUNT_KEY), eq(0));
+
+ // change only month and year, date remains the same
+ Mockito.clearInvocations(mEditor);
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR + 2, MONTH + 2, DATE + 1));
+ mSlicePurchaseController.updateNotificationCounts();
+ verify(mEditor).putInt(eq(DAILY_NOTIFICATION_COUNT_KEY), eq(0));
+ verify(mEditor).putInt(eq(MONTHLY_NOTIFICATION_COUNT_KEY), eq(0));
+
+ // change only date and year, month remains the same
+ Mockito.clearInvocations(mEditor);
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR + 3, MONTH + 2, DATE + 2));
+ mSlicePurchaseController.updateNotificationCounts();
+ verify(mEditor).putInt(eq(DAILY_NOTIFICATION_COUNT_KEY), eq(0));
+ verify(mEditor).putInt(eq(MONTHLY_NOTIFICATION_COUNT_KEY), eq(0));
+
+ // change only date and month, year remains the same
+ Mockito.clearInvocations(mEditor);
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR + 3, MONTH + 3, DATE + 3));
+ mSlicePurchaseController.updateNotificationCounts();
+ verify(mEditor).putInt(eq(DAILY_NOTIFICATION_COUNT_KEY), eq(0));
+ verify(mEditor).putInt(eq(MONTHLY_NOTIFICATION_COUNT_KEY), eq(0));
+ }
+
+ @Test
public void testPurchasePremiumCapabilityResultFeatureNotSupported() {
mSlicePurchaseController.purchasePremiumCapability(
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, TAG,
@@ -237,7 +307,6 @@
new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY});
mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING,
SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
mSlicePurchaseController.purchasePremiumCapability(
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, TAG,
@@ -255,7 +324,6 @@
new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY});
mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING,
SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
mSlicePurchaseController.purchasePremiumCapability(
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, TAG,
@@ -285,7 +353,6 @@
new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY});
mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING,
SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mPhone).getSubId();
mSlicePurchaseController.purchasePremiumCapability(
@@ -314,7 +381,6 @@
new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY});
mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING,
SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mPhone).getSubId();
doReturn(TelephonyManager.NETWORK_TYPE_NR).when(mServiceState).getDataNetworkType();
doReturn(null).when(mPremiumNetworkEntitlementApi).checkEntitlementStatus(anyInt());
@@ -342,8 +408,6 @@
// retry with provisioning response
mEntitlementResponse.mProvisionStatus =
PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_PROVISION_STATUS_IN_PROGRESS;
- doReturn(mEntitlementResponse).when(mPremiumNetworkEntitlementApi)
- .checkEntitlementStatus(anyInt());
mSlicePurchaseController.purchasePremiumCapability(
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, TAG,
@@ -357,12 +421,9 @@
PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_PROVISION_STATUS_NOT_PROVISIONED;
mEntitlementResponse.mEntitlementStatus =
PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCOMPATIBLE;
- doReturn(mEntitlementResponse).when(mPremiumNetworkEntitlementApi)
- .checkEntitlementStatus(anyInt());
mBundle.putLong(CarrierConfigManager
.KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
PURCHASE_CONDITION_TIMEOUT);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
mSlicePurchaseController.purchasePremiumCapability(
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, TAG,
@@ -409,12 +470,13 @@
public void testPurchasePremiumCapabilityResultSuccess() {
sendValidPurchaseRequest();
+ // broadcast SUCCESS response from slice purchase application
Intent intent = new Intent();
intent.setAction("com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_SUCCESS");
intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
- mBroadcastReceiver.onReceive(mMockedContext, intent);
+ mContext.getBroadcastReceiver().onReceive(mContext, intent);
mTestableLooper.processAllMessages();
assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS, mResult);
@@ -443,13 +505,7 @@
public void testPurchasePremiumCapabilityResultAlreadyPurchased() {
testPurchasePremiumCapabilityResultSuccess();
- // TODO: implement slicing config logic properly
- NetworkSlicingConfig slicingConfig = new NetworkSlicingConfig(Collections.emptyList(),
- Collections.singletonList(new NetworkSliceInfo.Builder()
- .setStatus(NetworkSliceInfo.SLICE_STATUS_ALLOWED).build()));
- mSlicePurchaseController.obtainMessage(2 /* EVENT_SLICING_CONFIG_CHANGED */,
- new AsyncResult(null, slicingConfig, null)).sendToTarget();
- mTestableLooper.processAllMessages();
+ sendNetworkSlicingConfig(true);
mSlicePurchaseController.purchasePremiumCapability(
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, TAG,
@@ -467,10 +523,7 @@
mResult);
// retry to verify purchase expired
- slicingConfig = new NetworkSlicingConfig(Collections.emptyList(), Collections.emptyList());
- mSlicePurchaseController.obtainMessage(2 /* EVENT_SLICING_CONFIG_CHANGED */,
- new AsyncResult(null, slicingConfig, null)).sendToTarget();
- mTestableLooper.processAllMessages();
+ sendNetworkSlicingConfig(false);
testPurchasePremiumCapabilityResultSuccess();
}
@@ -501,12 +554,13 @@
public void testPurchasePremiumCapabilityResultUserCanceled() {
sendValidPurchaseRequest();
+ // broadcast CANCELED response from slice purchase application
Intent intent = new Intent();
intent.setAction("com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_CANCELED");
intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
- mBroadcastReceiver.onReceive(mMockedContext, intent);
+ mContext.getBroadcastReceiver().onReceive(mContext, intent);
mTestableLooper.processAllMessages();
assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED, mResult);
@@ -528,6 +582,7 @@
public void testPurchasePremiumCapabilityResultCarrierError() {
sendValidPurchaseRequest();
+ // broadcast CARRIER_ERROR response from slice purchase application
Intent intent = new Intent();
intent.setAction(
"com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_CARRIER_ERROR");
@@ -536,7 +591,7 @@
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
intent.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE,
SlicePurchaseController.FAILURE_CODE_SERVER_UNREACHABLE);
- mBroadcastReceiver.onReceive(mMockedContext, intent);
+ mContext.getBroadcastReceiver().onReceive(mContext, intent);
mTestableLooper.processAllMessages();
assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR, mResult);
@@ -558,13 +613,14 @@
public void testPurchasePremiumCapabilityResultRequestFailed() {
sendValidPurchaseRequest();
+ // broadcast REQUEST_FAILED response from slice purchase application
Intent intent = new Intent();
intent.setAction(
"com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_REQUEST_FAILED");
intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
- mBroadcastReceiver.onReceive(mMockedContext, intent);
+ mContext.getBroadcastReceiver().onReceive(mContext, intent);
mTestableLooper.processAllMessages();
assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED, mResult);
@@ -582,7 +638,7 @@
intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
- mBroadcastReceiver.onReceive(mMockedContext, intent);
+ mContext.getBroadcastReceiver().onReceive(mContext, intent);
mTestableLooper.processAllMessages();
assertEquals(
TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION,
@@ -592,8 +648,70 @@
testPurchasePremiumCapabilityResultSuccess();
}
+ @Test
+ public void testPurchasePremiumCapabilityResultNotificationThrottled() {
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR, MONTH, DATE));
+ mSlicePurchaseController.updateNotificationCounts();
+
+ for (int count = 1; count <= DAILY_NOTIFICATION_MAX; count++) {
+ completeSuccessfulPurchase();
+ verify(mEditor).putInt(eq(DAILY_NOTIFICATION_COUNT_KEY), eq(count));
+ verify(mEditor).putInt(eq(MONTHLY_NOTIFICATION_COUNT_KEY), eq(count));
+ }
+
+ // retry to verify throttled
+ mSlicePurchaseController.purchasePremiumCapability(
+ TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, TAG,
+ mHandler.obtainMessage());
+ mTestableLooper.processAllMessages();
+ assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, mResult);
+
+ // change the date to trigger daily reset
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR, MONTH, DATE + 1));
+ Mockito.clearInvocations(mEditor);
+
+ for (int count = 1; count <= (MONTHLY_NOTIFICATION_MAX - DAILY_NOTIFICATION_MAX); count++) {
+ completeSuccessfulPurchase();
+ verify(mEditor).putInt(eq(DAILY_NOTIFICATION_COUNT_KEY), eq(count));
+ verify(mEditor).putInt(eq(MONTHLY_NOTIFICATION_COUNT_KEY),
+ eq(count + DAILY_NOTIFICATION_MAX));
+ }
+
+ // retry to verify throttled
+ mSlicePurchaseController.purchasePremiumCapability(
+ TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, TAG,
+ mHandler.obtainMessage());
+ mTestableLooper.processAllMessages();
+ assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, mResult);
+ }
+
+ private void completeSuccessfulPurchase() {
+ sendValidPurchaseRequest();
+
+ // broadcast NOTIFICATION_SHOWN response from slice purchase application
+ Intent intent = new Intent();
+ intent.setAction(
+ "com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN");
+ intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
+ intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+ TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+ mContext.getBroadcastReceiver().onReceive(mContext, intent);
+ mTestableLooper.processAllMessages();
+
+ // broadcast SUCCESS response from slice purchase application
+ intent.setAction("com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_SUCCESS");
+ mContext.getBroadcastReceiver().onReceive(mContext, intent);
+ mTestableLooper.processAllMessages();
+ assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS, mResult);
+
+ // complete network setup
+ sendNetworkSlicingConfig(true);
+ // purchase expired
+ sendNetworkSlicingConfig(false);
+ }
+
private void sendValidPurchaseRequest() {
- clearInvocations(mMockedContext);
+ clearInvocations(mContext);
// feature supported
doReturn((int) TelephonyManager.NETWORK_TYPE_BITMASK_NR).when(mPhone)
@@ -614,7 +732,6 @@
mBundle.putLong(CarrierConfigManager
.KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
PURCHASE_CONDITION_TIMEOUT);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
// default data subscription
doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mPhone).getSubId();
// network available
@@ -622,8 +739,6 @@
// entitlement check passed
mEntitlementResponse.mEntitlementStatus =
PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED;
- doReturn(mEntitlementResponse).when(mPremiumNetworkEntitlementApi)
- .checkEntitlementStatus(anyInt());
// send purchase request
mSlicePurchaseController.purchasePremiumCapability(
@@ -632,18 +747,23 @@
mTestableLooper.processAllMessages();
// verify that the purchase request was sent successfully
- ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mMockedContext).sendBroadcast(intentCaptor.capture());
- Intent intent = intentCaptor.getValue();
- assertEquals(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP, intent.getAction());
+ verify(mContext).sendBroadcast(any(Intent.class));
+ assertEquals(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP,
+ mContext.getBroadcast().getAction());
assertTrue(mSlicePurchaseController.hasMessages(4 /* EVENT_PURCHASE_TIMEOUT */,
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY));
+ verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
+ }
- // capture the broadcast receiver to fake responses from the slice purchase application
- ArgumentCaptor<SlicePurchaseControllerBroadcastReceiver> broadcastReceiverCaptor =
- ArgumentCaptor.forClass(SlicePurchaseControllerBroadcastReceiver.class);
- verify(mMockedContext).registerReceiver(
- broadcastReceiverCaptor.capture(), any(IntentFilter.class));
- mBroadcastReceiver = broadcastReceiverCaptor.getValue();
+ private void sendNetworkSlicingConfig(boolean configExists) {
+ // TODO: implement slicing config logic properly
+ NetworkSlicingConfig slicingConfig = new NetworkSlicingConfig(Collections.emptyList(),
+ configExists
+ ? Collections.singletonList(new NetworkSliceInfo.Builder()
+ .setStatus(NetworkSliceInfo.SLICE_STATUS_ALLOWED).build())
+ : Collections.emptyList());
+ mSlicePurchaseController.obtainMessage(2 /* EVENT_SLICING_CONFIG_CHANGED */,
+ new AsyncResult(null, slicingConfig, null)).sendToTarget();
+ mTestableLooper.processAllMessages();
}
}