Merge changes from topic "purchase_url"

* changes:
  Slice purchase app get URL from intent
  Slice purchase application update notification on locale change
This commit is contained in:
Sarah Chin
2022-12-19 18:57:39 +00:00
committed by Android (Google) Code Review
7 changed files with 279 additions and 168 deletions

View File

@@ -77,6 +77,7 @@
<receiver android:name="com.android.carrierdefaultapp.SlicePurchaseBroadcastReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.LOCALE_CHANGED" />
<action android:name="com.android.phone.slice.action.START_SLICE_PURCHASE_APP" />
<action android:name="com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_TIMEOUT" />
<action android:name="com.android.phone.slice.action.NOTIFICATION_CANCELED" />

View File

@@ -19,21 +19,17 @@ package com.android.carrierdefaultapp;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.KeyEvent;
import android.webkit.URLUtil;
import android.webkit.WebView;
import com.android.phone.slice.SlicePurchaseController;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.TimeUnit;
@@ -62,38 +58,29 @@ public class SlicePurchaseActivity extends Activity {
@NonNull private WebView mWebView;
@NonNull private Context mApplicationContext;
@NonNull private Intent mIntent;
@Nullable private URL mUrl;
private int mSubId;
@NonNull private URL mUrl;
@TelephonyManager.PremiumCapability protected int mCapability;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mIntent = getIntent();
mSubId = mIntent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
int subId = mIntent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mCapability = mIntent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
String url = mIntent.getStringExtra(SlicePurchaseController.EXTRA_PURCHASE_URL);
mApplicationContext = getApplicationContext();
mUrl = getUrl();
logd("onCreate: subId=" + mSubId + ", capability="
+ TelephonyManager.convertPremiumCapabilityToString(mCapability)
+ ", url=" + mUrl);
logd("onCreate: subId=" + subId + ", capability="
+ TelephonyManager.convertPremiumCapabilityToString(mCapability) + ", url=" + url);
// Cancel network boost notification
mApplicationContext.getSystemService(NotificationManager.class)
.cancel(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability);
SlicePurchaseBroadcastReceiver.cancelNotification(mApplicationContext, mCapability);
// Verify intent and values are valid
if (!SlicePurchaseBroadcastReceiver.isIntentValid(mIntent)) {
loge("Not starting SlicePurchaseActivity with an invalid Intent: " + mIntent);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
mIntent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
finishAndRemoveTask();
return;
}
// Verify purchase URL is valid
mUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url);
if (mUrl == null) {
String error = "Unable to create a URL from carrier configs.";
String error = "Unable to create a purchase URL.";
loge(error);
Intent data = new Intent();
data.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE,
@@ -104,18 +91,26 @@ public class SlicePurchaseActivity extends Activity {
finishAndRemoveTask();
return;
}
if (mSubId != SubscriptionManager.getDefaultSubscriptionId()) {
// Verify intent is valid
if (!SlicePurchaseBroadcastReceiver.isIntentValid(mIntent)) {
loge("Not starting SlicePurchaseActivity with an invalid Intent: " + mIntent);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
mIntent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
finishAndRemoveTask();
return;
}
// Verify sub ID is valid
if (subId != SubscriptionManager.getDefaultSubscriptionId()) {
loge("Unable to start the slice purchase application on the non-default data "
+ "subscription: " + mSubId);
+ "subscription: " + subId);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
mIntent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION);
finishAndRemoveTask();
return;
}
// Create a reference to this activity in SlicePurchaseBroadcastReceiver
SlicePurchaseBroadcastReceiver.updateSlicePurchaseActivity(mCapability, this);
// Create and configure WebView
setupWebView();
}
@@ -161,28 +156,9 @@ public class SlicePurchaseActivity extends Activity {
logd("onDestroy: User canceled the purchase by closing the application.");
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
mIntent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity(mCapability);
super.onDestroy();
}
@Nullable private URL getUrl() {
String url = mApplicationContext.getSystemService(CarrierConfigManager.class)
.getConfigForSubId(mSubId).getString(
CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
boolean isUrlValid = URLUtil.isValidUrl(url);
if (URLUtil.isAssetUrl(url)) {
isUrlValid = url.equals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
}
if (isUrlValid) {
try {
return new URL(url);
} catch (MalformedURLException ignored) {
}
}
loge("Invalid URL: " + url);
return null;
}
private void setupWebView() {
// Create WebView
mWebView = new WebView(this);

View File

@@ -16,6 +16,7 @@
package com.android.carrierdefaultapp;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -24,20 +25,28 @@ import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.os.LocaleList;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.telephony.AnomalyReporter;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.URLUtil;
import android.webkit.WebView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.phone.slice.SlicePurchaseController;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
@@ -57,10 +66,6 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
*/
private static final String UUID_BAD_PENDING_INTENT = "c360246e-95dc-4abf-9dc1-929a76cd7e53";
/** Weak references to {@link SlicePurchaseActivity} for each capability, if it exists. */
private static final Map<Integer, WeakReference<SlicePurchaseActivity>>
sSlicePurchaseActivities = new HashMap<>();
/** Channel ID for the network boost notification. */
private static final String NETWORK_BOOST_NOTIFICATION_CHANNEL_ID = "network_boost";
/** Tag for the network boost notification. */
@@ -70,27 +75,28 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
"com.android.phone.slice.action.NOTIFICATION_CANCELED";
/**
* Create a weak reference to {@link SlicePurchaseActivity}. The reference will be removed when
* {@link SlicePurchaseActivity#onDestroy()} is called.
*
* @param capability The premium capability requested.
* @param slicePurchaseActivity The instance of SlicePurchaseActivity.
* A map of Intents sent by {@link SlicePurchaseController} for each capability.
* If this map contains an Intent for a given capability, the network boost notification to
* purchase the capability is visible to the user.
* If this map does not contain an Intent for a given capability, either the capability was
* never requested or the {@link SlicePurchaseActivity} is visible to the user.
* An Intent is added to this map when the network boost notification is displayed to the user
* and removed from the map when the notification is canceled.
*/
public static void updateSlicePurchaseActivity(
@TelephonyManager.PremiumCapability int capability,
@NonNull SlicePurchaseActivity slicePurchaseActivity) {
sSlicePurchaseActivities.put(capability, new WeakReference<>(slicePurchaseActivity));
}
private static final Map<Integer, Intent> sIntents = new HashMap<>();
/**
* Remove the weak reference to {@link SlicePurchaseActivity} when
* {@link SlicePurchaseActivity#onDestroy()} is called.
* Cancel the network boost notification for the given capability and
* remove the corresponding notification intent from the map.
*
* @param capability The premium capability requested.
* @param context The context to cancel the notification in.
* @param capability The premium capability to cancel the notification for.
*/
public static void removeSlicePurchaseActivity(
public static void cancelNotification(@NonNull Context context,
@TelephonyManager.PremiumCapability int capability) {
sSlicePurchaseActivities.remove(capability);
context.getSystemService(NotificationManager.class).cancelAsUser(
NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
sIntents.remove(capability);
}
/**
@@ -139,7 +145,7 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
* Check whether the Intent is valid and can be used to complete purchases in the slice purchase
* application. This checks that all necessary extras exist and that the values are valid.
*
* @param intent The intent to check
* @param intent The intent to check.
* @return {@code true} if the intent is valid and {@code false} otherwise.
*/
public static boolean isIntentValid(@NonNull Intent intent) {
@@ -164,6 +170,12 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
return false;
}
String purchaseUrl = intent.getStringExtra(SlicePurchaseController.EXTRA_PURCHASE_URL);
if (getPurchaseUrl(purchaseUrl) == null) {
loge("isIntentValid: invalid purchase URL: " + purchaseUrl);
return false;
}
String appName = intent.getStringExtra(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME);
if (TextUtils.isEmpty(appName)) {
loge("isIntentValid: empty requesting application name: " + appName);
@@ -180,6 +192,30 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
}
/**
* Get the {@link URL} from the given purchase URL String, if it is valid.
*
* @param purchaseUrl The purchase URL String to use to create the URL.
* @return The purchase URL from the given String or {@code null} if it is invalid.
*/
@Nullable public static URL getPurchaseUrl(@Nullable String purchaseUrl) {
if (!URLUtil.isValidUrl(purchaseUrl)) {
return null;
}
if (URLUtil.isAssetUrl(purchaseUrl)
&& !purchaseUrl.equals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE)) {
return null;
}
URL url = null;
try {
url = new URL(purchaseUrl);
url.toURI();
} catch (MalformedURLException | URISyntaxException e) {
loge("Invalid purchase URL: " + purchaseUrl + ", " + e);
}
return url;
}
private static boolean isPendingIntentValid(@NonNull Intent intent, @NonNull String extra) {
String intentType = getPendingIntentType(extra);
PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class);
@@ -223,8 +259,11 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
logd("onReceive intent: " + intent.getAction());
switch (intent.getAction()) {
case Intent.ACTION_LOCALE_CHANGED:
onLocaleChanged(context);
break;
case SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP:
onDisplayNetworkBoostNotification(context, intent);
onDisplayNetworkBoostNotification(context, intent, false);
break;
case SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT:
onTimeout(context, intent);
@@ -237,17 +276,31 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
}
}
private void onLocaleChanged(@NonNull Context context) {
if (sIntents.isEmpty()) return;
for (int capability : new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY}) {
if (sIntents.get(capability) != null) {
// Notification is active -- update notification for new locale
context.getSystemService(NotificationManager.class).cancelAsUser(
NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
onDisplayNetworkBoostNotification(context, sIntents.get(capability), true);
}
}
}
private void onDisplayNetworkBoostNotification(@NonNull Context context,
@NonNull Intent intent) {
if (!isIntentValid(intent)) {
@NonNull Intent intent, boolean repeat) {
if (!repeat && !isIntentValid(intent)) {
sendSlicePurchaseAppResponse(intent,
SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
return;
}
Resources res = getResources(context);
NotificationChannel channel = new NotificationChannel(
NETWORK_BOOST_NOTIFICATION_CHANNEL_ID,
context.getResources().getString(R.string.network_boost_notification_channel),
res.getString(R.string.network_boost_notification_channel),
NotificationManager.IMPORTANCE_DEFAULT);
// CarrierDefaultApp notifications are unblockable by default. Make this channel blockable
// to allow users to disable notifications posted to this channel without affecting other
@@ -257,12 +310,11 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
Notification notification =
new Notification.Builder(context, NETWORK_BOOST_NOTIFICATION_CHANNEL_ID)
.setContentTitle(String.format(context.getResources().getString(
.setContentTitle(String.format(res.getString(
R.string.network_boost_notification_title),
intent.getStringExtra(
SlicePurchaseController.EXTRA_REQUESTING_APP_NAME)))
.setContentText(context.getResources().getString(
R.string.network_boost_notification_detail))
.setContentText(res.getString(R.string.network_boost_notification_detail))
.setSmallIcon(R.drawable.ic_network_boost)
.setContentIntent(createContentIntent(context, intent, 1))
.setDeleteIntent(intent.getParcelableExtra(
@@ -271,26 +323,56 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
// the user canceling or closing the notification.
.addAction(new Notification.Action.Builder(
Icon.createWithResource(context, R.drawable.ic_network_boost),
context.getResources().getString(
R.string.network_boost_notification_button_not_now),
res.getString(R.string.network_boost_notification_button_not_now),
createCanceledIntent(context, intent)).build())
// Add an action for the "Manage" button, which has the same behavior as
// the user clicking on the notification.
.addAction(new Notification.Action.Builder(
Icon.createWithResource(context, R.drawable.ic_network_boost),
context.getResources().getString(
R.string.network_boost_notification_button_manage),
res.getString(R.string.network_boost_notification_button_manage),
createContentIntent(context, intent, 2)).build())
.build();
int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
logd("Display the network boost notification for capability "
logd((repeat ? "Update" : "Display") + " the network boost notification for capability "
+ TelephonyManager.convertPremiumCapabilityToString(capability));
context.getSystemService(NotificationManager.class).notifyAsUser(
NETWORK_BOOST_NOTIFICATION_TAG, capability, notification, UserHandle.ALL);
sendSlicePurchaseAppResponse(intent,
SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
if (!repeat) {
sIntents.put(capability, intent);
sendSlicePurchaseAppResponse(intent,
SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
}
}
/**
* Get the {@link Resources} for the current locale.
*
* @param context The context to get the resources in.
*
* @return The resources in the current locale.
*/
@VisibleForTesting
@NonNull public Resources getResources(@NonNull Context context) {
Resources resources = context.getResources();
Configuration config = resources.getConfiguration();
config.setLocale(getCurrentLocale());
return new Resources(resources.getAssets(), resources.getDisplayMetrics(), config);
}
/**
* Get the current {@link Locale} from the system property {@code persist.sys.locale}.
*
* @return The user's default/preferred language.
*/
@VisibleForTesting
@NonNull public Locale getCurrentLocale() {
String languageTag = SystemProperties.get("persist.sys.locale");
if (TextUtils.isEmpty(languageTag)) {
return LocaleList.getAdjustedDefault().get(0);
}
return Locale.forLanguageTag(languageTag);
}
/**
@@ -343,16 +425,13 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
logd("Purchase capability " + TelephonyManager.convertPremiumCapabilityToString(capability)
+ " timed out.");
if (sSlicePurchaseActivities.get(capability) == null) {
// Notification is still active
if (sIntents.get(capability) != null) {
// Notification is still active -- cancel pending notification
logd("Closing network boost notification since the user did not respond in time.");
context.getSystemService(NotificationManager.class).cancelAsUser(
NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
cancelNotification(context, capability);
} else {
// Notification was dismissed but SlicePurchaseActivity is still active
logd("Closing slice purchase application WebView since the user did not complete the "
+ "purchase in time.");
sSlicePurchaseActivities.get(capability).get().finishAndRemoveTask();
// SlicePurchaseActivity is still active -- ignore timer
logd("Ignoring timeout since the SlicePurchaseActivity is still active.");
}
}
@@ -360,8 +439,7 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
logd("onUserCanceled: " + TelephonyManager.convertPremiumCapabilityToString(capability));
context.getSystemService(NotificationManager.class)
.cancelAsUser(NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
cancelNotification(context, capability);
sendSlicePurchaseAppResponse(intent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
}

View File

@@ -37,5 +37,6 @@ android_test {
// Include all test java files.
srcs: ["src/**/*.java"],
platform_apis: true,
use_embedded_native_libs: false,
instrumentation_for: "CarrierDefaultApp",
}

View File

@@ -17,7 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.carrierdefaultapp.tests.unit">
<uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
<application>
<application android:extractNativeLibs="true">
<uses-library android:name="android.test.runner" />
</application>

View File

@@ -39,7 +39,6 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.phone.slice.SlicePurchaseController;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -94,6 +93,8 @@ public class SlicePurchaseActivityTest extends ActivityUnitTestCase<SlicePurchas
SubscriptionManager.getDefaultDataSubscriptionId());
intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
intent.putExtra(SlicePurchaseController.EXTRA_PURCHASE_URL,
SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
intent.putExtra(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME, TAG);
Intent spiedIntent = spy(intent);
@@ -110,12 +111,6 @@ public class SlicePurchaseActivityTest extends ActivityUnitTestCase<SlicePurchas
mSlicePurchaseActivity = startActivity(spiedIntent, null, null);
}
@After
public void tearDown() throws Exception {
mSlicePurchaseActivity.onDestroy();
super.tearDown();
}
@Test
public void testOnPurchaseSuccessful() throws Exception {
int duration = 5 * 60 * 1000; // 5 minutes

View File

@@ -18,10 +18,14 @@ package com.android.carrierdefaultapp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
@@ -35,11 +39,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.UserHandle;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.DisplayMetrics;
import androidx.test.runner.AndroidJUnit4;
@@ -52,6 +56,9 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.net.URL;
import java.util.Locale;
@RunWith(AndroidJUnit4.class)
public class SlicePurchaseBroadcastReceiverTest {
private static final int PHONE_ID = 0;
@@ -67,24 +74,26 @@ public class SlicePurchaseBroadcastReceiverTest {
@Mock PendingIntent mNotificationShownIntent;
@Mock Context mContext;
@Mock Resources mResources;
@Mock Configuration mConfiguration;
@Mock NotificationManager mNotificationManager;
@Mock ApplicationInfo mApplicationInfo;
@Mock PackageManager mPackageManager;
@Mock DisplayMetrics mDisplayMetrics;
@Mock SlicePurchaseActivity mSlicePurchaseActivity;
private SlicePurchaseBroadcastReceiver mSlicePurchaseBroadcastReceiver;
private ArgumentCaptor<Intent> mIntentCaptor;
private ArgumentCaptor<Notification> mNotificationCaptor;
private Resources mSpiedResources;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mSpiedResources = spy(Resources.getSystem());
doReturn("").when(mResources).getString(anyInt());
doReturn(mNotificationManager).when(mContext)
.getSystemService(eq(NotificationManager.class));
doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
doReturn(mPackageManager).when(mContext).getPackageManager();
doReturn(mSpiedResources).when(mContext).getResources();
mIntentCaptor = ArgumentCaptor.forClass(Intent.class);
mNotificationCaptor = ArgumentCaptor.forClass(Notification.class);
mSlicePurchaseBroadcastReceiver = spy(new SlicePurchaseBroadcastReceiver());
}
@@ -109,8 +118,9 @@ public class SlicePurchaseBroadcastReceiverTest {
eq(EXTRA), eq(PendingIntent.class));
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(
mContext, mIntent, EXTRA, mDataIntent);
verify(mPendingIntent).send(eq(mContext), eq(0), mIntentCaptor.capture());
assertEquals(mDataIntent, mIntentCaptor.getValue());
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mPendingIntent).send(eq(mContext), eq(0), captor.capture());
assertEquals(mDataIntent, captor.getValue());
}
@Test
@@ -124,6 +134,8 @@ public class SlicePurchaseBroadcastReceiverTest {
eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
doReturn(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).when(mIntent).getStringExtra(
eq(SlicePurchaseController.EXTRA_PURCHASE_URL));
doReturn(TAG).when(mIntent).getStringExtra(
eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
@@ -137,18 +149,53 @@ public class SlicePurchaseBroadcastReceiverTest {
}
@Test
public void testDisplayNetworkBoostNotification() throws Exception {
// set up intent
doReturn(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP).when(mIntent).getAction();
doReturn(PHONE_ID).when(mIntent).getIntExtra(
eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
doReturn(TAG).when(mIntent).getStringExtra(
eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
public void testGetPurchaseUrl() {
String[] invalidUrls = new String[] {
null,
"",
"www.google.com",
"htt://www.google.com",
"http//www.google.com",
"http:/www.google.com",
"file:///android_asset/",
"file:///android_asset/slice_store_test.html"
};
for (String url : invalidUrls) {
URL purchaseUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url);
assertNull(purchaseUrl);
}
assertEquals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE,
SlicePurchaseBroadcastReceiver.getPurchaseUrl(
SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).toString());
}
@Test
public void testDisplayNetworkBoostNotification() throws Exception {
displayNetworkBoostNotification();
// verify network boost notification was shown
ArgumentCaptor<Notification> captor = ArgumentCaptor.forClass(Notification.class);
verify(mNotificationManager).notifyAsUser(
eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
captor.capture(),
eq(UserHandle.ALL));
// verify notification fields
Notification notification = captor.getValue();
assertEquals(mContentIntent1, notification.contentIntent);
assertEquals(mPendingIntent, notification.deleteIntent);
assertEquals(2, notification.actions.length);
assertEquals(mCanceledIntent, notification.actions[0].actionIntent);
assertEquals(mContentIntent2, notification.actions[1].actionIntent);
// verify SlicePurchaseController was notified
verify(mNotificationShownIntent).send();
}
private void displayNetworkBoostNotification() {
// set up pending intents
doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
doReturn(true).when(mPendingIntent).isBroadcast();
@@ -161,14 +208,7 @@ public class SlicePurchaseBroadcastReceiverTest {
eq(SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN),
eq(PendingIntent.class));
// set up notification
doReturn(mResources).when(mContext).getResources();
doReturn(mDisplayMetrics).when(mResources).getDisplayMetrics();
doReturn("").when(mResources).getString(anyInt());
doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
doReturn(mPackageManager).when(mContext).getPackageManager();
// set up intents created by broadcast receiver
// spy notification intents to prevent PendingIntent issues
doReturn(mContentIntent1).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
eq(mContext), eq(mIntent), eq(1));
doReturn(mContentIntent2).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
@@ -176,35 +216,30 @@ public class SlicePurchaseBroadcastReceiverTest {
doReturn(mCanceledIntent).when(mSlicePurchaseBroadcastReceiver).createCanceledIntent(
eq(mContext), eq(mIntent));
// spy resources to prevent resource not found issues
doReturn(mResources).when(mSlicePurchaseBroadcastReceiver).getResources(eq(mContext));
// send ACTION_START_SLICE_PURCHASE_APP
doReturn(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP).when(mIntent).getAction();
doReturn(PHONE_ID).when(mIntent).getIntExtra(
eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
doReturn(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).when(mIntent).getStringExtra(
eq(SlicePurchaseController.EXTRA_PURCHASE_URL));
doReturn(TAG).when(mIntent).getStringExtra(
eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
// verify network boost notification was shown
verify(mNotificationManager).notifyAsUser(
eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
mNotificationCaptor.capture(),
eq(UserHandle.ALL));
Notification notification = mNotificationCaptor.getValue();
assertEquals(mContentIntent1, notification.contentIntent);
assertEquals(mPendingIntent, notification.deleteIntent);
assertEquals(2, notification.actions.length);
assertEquals(mCanceledIntent, notification.actions[0].actionIntent);
assertEquals(mContentIntent2, notification.actions[1].actionIntent);
// verify SlicePurchaseController was notified
verify(mNotificationShownIntent).send();
}
@Test
public void testNotificationCanceled() {
// set up intent
// send ACTION_NOTIFICATION_CANCELED
doReturn("com.android.phone.slice.action.NOTIFICATION_CANCELED").when(mIntent).getAction();
doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
// send ACTION_NOTIFICATION_CANCELED
mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
// verify notification was canceled
@@ -215,14 +250,14 @@ public class SlicePurchaseBroadcastReceiverTest {
}
@Test
public void testNotificationTimeout() {
// set up intent
public void testNotificationTimeout() throws Exception {
displayNetworkBoostNotification();
// send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent)
.getAction();
doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
// send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
// verify notification was canceled
@@ -233,27 +268,52 @@ public class SlicePurchaseBroadcastReceiverTest {
}
@Test
// TODO: WebView/Activity should not close on timeout.
// This test should be removed once implementation is fixed.
public void testActivityTimeout() {
// create and track activity
SlicePurchaseBroadcastReceiver.updateSlicePurchaseActivity(
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, mSlicePurchaseActivity);
public void testLocaleChanged() throws Exception {
// get previous locale
doReturn(mConfiguration).when(mSpiedResources).getConfiguration();
Locale before = getLocale();
// set up intent
doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent)
.getAction();
doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
// display notification
displayNetworkBoostNotification();
clearInvocations(mNotificationManager);
clearInvocations(mNotificationShownIntent);
// send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
// change current locale from previous value
Locale newLocale = Locale.forLanguageTag("en-US");
if (before.equals(newLocale)) {
newLocale = Locale.forLanguageTag("ko-KR");
}
doReturn(newLocale).when(mSlicePurchaseBroadcastReceiver).getCurrentLocale();
// send ACTION_LOCALE_CHANGED
doReturn(Intent.ACTION_LOCALE_CHANGED).when(mIntent).getAction();
mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
// verify activity was canceled
verify(mSlicePurchaseActivity).finishAndRemoveTask();
// verify notification was updated and SlicePurchaseController was not notified
verify(mNotificationManager).cancelAsUser(
eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
eq(UserHandle.ALL));
verify(mNotificationManager).notifyAsUser(
eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
any(Notification.class),
eq(UserHandle.ALL));
verify(mNotificationShownIntent, never()).send();
// untrack activity
SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity(
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
// verify locale was changed successfully
doCallRealMethod().when(mSlicePurchaseBroadcastReceiver).getResources(eq(mContext));
assertEquals(newLocale, getLocale());
}
private Locale getLocale() {
try {
mSlicePurchaseBroadcastReceiver.getResources(mContext);
fail("getLocale should not have completed successfully.");
} catch (NullPointerException expected) { }
ArgumentCaptor<Locale> captor = ArgumentCaptor.forClass(Locale.class);
verify(mConfiguration).setLocale(captor.capture());
clearInvocations(mConfiguration);
return captor.getValue();
}
}