diff options
author | 2022-12-19 18:57:39 +0000 | |
---|---|---|
committer | 2022-12-19 18:57:39 +0000 | |
commit | 152f1c5c0fc8cde2ee38cc3cf0fc63753afe32e6 (patch) | |
tree | e899d085185649b0501ddbe925a5f75bb63ed1f0 | |
parent | ef4cbddd832cf6fad8a4e372c47836739ebc57c0 (diff) | |
parent | f09e5e1cc8795fd0542b321852abf5914ee7364e (diff) |
Merge changes from topic "purchase_url"
* changes:
Slice purchase app get URL from intent
Slice purchase application update notification on locale change
7 files changed, 280 insertions, 169 deletions
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml index c4bb17cb01dc..3f86aba26a1f 100644 --- a/packages/CarrierDefaultApp/AndroidManifest.xml +++ b/packages/CarrierDefaultApp/AndroidManifest.xml @@ -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" /> diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java index c524037fe444..c08b83a7ae78 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java @@ -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); diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java index 367ae06adfc7..30411381ae82 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java @@ -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); } diff --git a/packages/CarrierDefaultApp/tests/unit/Android.bp b/packages/CarrierDefaultApp/tests/unit/Android.bp index cdf795752e07..0d08ec6ca2b3 100644 --- a/packages/CarrierDefaultApp/tests/unit/Android.bp +++ b/packages/CarrierDefaultApp/tests/unit/Android.bp @@ -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", } diff --git a/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml b/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml index 7a26d95551df..995170a61614 100644 --- a/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml +++ b/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml @@ -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> diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java index cecc86d8e330..1bf644eb1fb6 100644 --- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java +++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java @@ -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 diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java index ab99a76902d9..958e13870759 100644 --- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java +++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java @@ -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 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 { - // 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)); + 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); - - // 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()); - - // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT + public void testLocaleChanged() throws Exception { + // get previous locale + doReturn(mConfiguration).when(mSpiedResources).getConfiguration(); + Locale before = getLocale(); + + // display notification + displayNetworkBoostNotification(); + clearInvocations(mNotificationManager); + clearInvocations(mNotificationShownIntent); + + // 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(); + + // verify locale was changed successfully + doCallRealMethod().when(mSlicePurchaseBroadcastReceiver).getResources(eq(mContext)); + assertEquals(newLocale, getLocale()); + } - // untrack activity - SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity( - TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY); + 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(); } } |