diff options
4 files changed, 152 insertions, 15 deletions
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java index b1009808cccc..fcc4ec122839 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java @@ -31,9 +31,11 @@ import android.webkit.CookieManager; import android.webkit.WebView; import android.webkit.WebViewClient; +import com.android.internal.annotations.VisibleForTesting; import com.android.phone.slice.SlicePurchaseController; import java.net.URL; +import java.util.Base64; /** * Activity that launches when the user clicks on the performance boost notification. @@ -56,11 +58,17 @@ import java.net.URL; public class SlicePurchaseActivity extends Activity { private static final String TAG = "SlicePurchaseActivity"; + private static final int CONTENTS_TYPE_UNSPECIFIED = 0; + private static final int CONTENTS_TYPE_JSON = 1; + private static final int CONTENTS_TYPE_XML = 2; + @NonNull private WebView mWebView; @NonNull private Context mApplicationContext; @NonNull private Intent mIntent; @NonNull private URL mUrl; @TelephonyManager.PremiumCapability protected int mCapability; + @Nullable private String mUserData; + private int mContentsType; private boolean mIsUserTriggeredFinish; @Override @@ -72,6 +80,7 @@ public class SlicePurchaseActivity extends Activity { mCapability = mIntent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY, SlicePurchaseController.PREMIUM_CAPABILITY_INVALID); String url = mIntent.getStringExtra(SlicePurchaseController.EXTRA_PURCHASE_URL); + mUserData = mIntent.getStringExtra(SlicePurchaseController.EXTRA_USER_DATA); mApplicationContext = getApplicationContext(); mIsUserTriggeredFinish = true; logd("onCreate: subId=" + subId + ", capability=" @@ -81,7 +90,17 @@ public class SlicePurchaseActivity extends Activity { SlicePurchaseBroadcastReceiver.cancelNotification(mApplicationContext, mCapability); // Verify purchase URL is valid - mUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url); + String contentsType = mIntent.getStringExtra(SlicePurchaseController.EXTRA_CONTENTS_TYPE); + mContentsType = CONTENTS_TYPE_UNSPECIFIED; + if (!TextUtils.isEmpty(contentsType)) { + if (contentsType.equals("json")) { + mContentsType = CONTENTS_TYPE_JSON; + } else if (contentsType.equals("xml")) { + mContentsType = CONTENTS_TYPE_XML; + } + } + mUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url, mUserData, + mContentsType == CONTENTS_TYPE_UNSPECIFIED); if (mUrl == null) { String error = "Unable to create a purchase URL."; loge(error); @@ -95,6 +114,20 @@ public class SlicePurchaseActivity extends Activity { return; } + // Verify user data exists if contents type is specified + if (mContentsType != CONTENTS_TYPE_UNSPECIFIED && TextUtils.isEmpty(mUserData)) { + String error = "Contents type was specified but user data does not exist."; + loge(error); + Intent data = new Intent(); + data.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE, + SlicePurchaseController.FAILURE_CODE_NO_USER_DATA); + data.putExtra(SlicePurchaseController.EXTRA_FAILURE_REASON, error); + SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext, + mIntent, SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data); + finishAndRemoveTask(); + return; + } + // Verify intent is valid if (!SlicePurchaseBroadcastReceiver.isIntentValid(mIntent)) { loge("Not starting SlicePurchaseActivity with an invalid Intent: " + mIntent); @@ -115,9 +148,7 @@ public class SlicePurchaseActivity extends Activity { } // Clear any cookies that might be persisted from previous sessions before loading WebView - CookieManager.getInstance().removeAllCookies(value -> { - setupWebView(); - }); + CookieManager.getInstance().removeAllCookies(value -> setupWebView()); } protected void onPurchaseSuccessful() { @@ -190,14 +221,46 @@ public class SlicePurchaseActivity extends Activity { // Display WebView setContentView(mWebView); - // Load the URL - String userData = mIntent.getStringExtra(SlicePurchaseController.EXTRA_USER_DATA); - if (TextUtils.isEmpty(userData)) { - logd("Starting WebView with url: " + mUrl.toString()); - mWebView.loadUrl(mUrl.toString()); + // Start the WebView + startWebView(mWebView, mUrl.toString(), mContentsType, mUserData); + } + + /** + * Send the URL to the WebView as either a GET or POST request, based on the contents type: + * <ul> + * <li> + * CONTENTS_TYPE_UNSPECIFIED: + * If the user data exists, append it to the purchase URL and load it as a GET request. + * If the user data does not exist, load just the purchase URL as a GET request. + * </li> + * <li> + * CONTENTS_TYPE_JSON or CONTENTS_TYPE_XML: + * The user data must exist. Send the JSON or XML formatted user data in a POST request. + * If the user data is encoded, it must be prefaced by {@code encodedValue=} and will be + * encoded in Base64. Decode the user data and send it in the POST request. + * </li> + * </ul> + * @param webView The WebView to start. + * @param url The URL to start the WebView with. + * @param contentsType The contents type of the userData. + * @param userData The user data to send with the GET or POST request, if it exists. + */ + @VisibleForTesting + public static void startWebView(@NonNull WebView webView, @NonNull String url, int contentsType, + @Nullable String userData) { + if (contentsType == CONTENTS_TYPE_UNSPECIFIED) { + logd("Starting WebView GET with url: " + url); + webView.loadUrl(url); } else { - logd("Starting WebView with url: " + mUrl.toString() + ", userData=" + userData); - mWebView.postUrl(mUrl.toString(), userData.getBytes()); + byte[] data = userData.getBytes(); + String[] split = userData.split("encodedValue="); + if (split.length > 1) { + logd("Decoding encoded value: " + split[1]); + data = Base64.getDecoder().decode(split[1]); + } + logd("Starting WebView POST with url: " + url + ", contentsType: " + contentsType + + ", data: " + new String(data)); + webView.postUrl(url, data); } } diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java index 23b976638eef..9b33704cc8e7 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java @@ -173,7 +173,9 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{ } String purchaseUrl = intent.getStringExtra(SlicePurchaseController.EXTRA_PURCHASE_URL); - if (getPurchaseUrl(purchaseUrl) == null) { + String userData = intent.getStringExtra(SlicePurchaseController.EXTRA_USER_DATA); + String contentsType = intent.getStringExtra(SlicePurchaseController.EXTRA_CONTENTS_TYPE); + if (getPurchaseUrl(purchaseUrl, userData, TextUtils.isEmpty(contentsType)) == null) { loge("isIntentValid: invalid purchase URL: " + purchaseUrl); return false; } @@ -195,12 +197,39 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{ } /** + * Get the {@link URL} from the given purchase URL String and user data, if it is valid. + * + * @param purchaseUrl The purchase URL String to use to create the URL. + * @param userData The user data parameter from the entitlement server. + * @param shouldAppendUserData If this is {@code true} and the {@code userData} exists, + * the {@code userData} should be appended to the {@code purchaseUrl} to create the URL. + * If this is false, only the {@code purchaseUrl} should be used and the {@code userData} + * will be sent as data to the POST request instead. + * @return The URL from the given purchase URL and user data or {@code null} if it is invalid. + */ + @Nullable public static URL getPurchaseUrl(@Nullable String purchaseUrl, + @Nullable String userData, boolean shouldAppendUserData) { + if (purchaseUrl == null) { + return null; + } + // Only append user data if it exists, otherwise just return the purchase URL + if (!shouldAppendUserData || TextUtils.isEmpty(userData)) { + return getPurchaseUrl(purchaseUrl); + } + URL url = getPurchaseUrl(purchaseUrl + "?" + userData); + if (url == null) { + url = getPurchaseUrl(purchaseUrl); + } + return url; + } + + /** * 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) { + @Nullable private static URL getPurchaseUrl(@Nullable String purchaseUrl) { if (!URLUtil.isValidUrl(purchaseUrl)) { return null; } 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 cc103fa98e65..1ec180beea81 100644 --- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java +++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java @@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.NotificationManager; @@ -33,6 +34,7 @@ import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.test.ActivityUnitTestCase; +import android.webkit.WebView; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -46,6 +48,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Base64; + @RunWith(AndroidJUnit4.class) public class SlicePurchaseActivityTest extends ActivityUnitTestCase<SlicePurchaseActivity> { private static final String CARRIER = "Some Carrier"; @@ -59,6 +63,7 @@ public class SlicePurchaseActivityTest extends ActivityUnitTestCase<SlicePurchas @Mock CarrierConfigManager mCarrierConfigManager; @Mock NotificationManager mNotificationManager; @Mock PersistableBundle mPersistableBundle; + @Mock WebView mWebView; private SlicePurchaseActivity mSlicePurchaseActivity; private Context mContext; @@ -153,4 +158,23 @@ public class SlicePurchaseActivityTest extends ActivityUnitTestCase<SlicePurchas mSlicePurchaseActivity.onDismissFlow(); verify(mRequestFailedIntent).send(); } + + @Test + public void testStartWebView() { + // unspecified contents type + SlicePurchaseActivity.startWebView(mWebView, URL, 0 /* CONTENTS_TYPE_UNSPECIFIED */, null); + verify(mWebView).loadUrl(eq(URL)); + + // specified contents type with user data + String userData = "userData"; + byte[] userDataBytes = userData.getBytes(); + SlicePurchaseActivity.startWebView(mWebView, URL, 1 /* CONTENTS_TYPE_JSON */, userData); + verify(mWebView).postUrl(eq(URL), eq(userDataBytes)); + + // specified contents type with encoded user data + byte[] encodedUserData = Base64.getEncoder().encode(userDataBytes); + userData = "encodedValue=" + new String(encodedUserData); + SlicePurchaseActivity.startWebView(mWebView, URL, 1 /* CONTENTS_TYPE_JSON */, userData); + verify(mWebView, times(2)).postUrl(eq(URL), eq(userDataBytes)); + } } 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 952789c56b2e..61847b517c8d 100644 --- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java +++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java @@ -160,14 +160,35 @@ public class SlicePurchaseBroadcastReceiverTest { "file:///android_asset/slice_store_test.html" }; + // test invalid URLs for (String url : invalidUrls) { - URL purchaseUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url); + URL purchaseUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url, null, false); assertNull(purchaseUrl); } + // test asset URL assertEquals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE, SlicePurchaseBroadcastReceiver.getPurchaseUrl( - SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).toString()); + SlicePurchaseController.SLICE_PURCHASE_TEST_FILE, null, false).toString()); + + // test normal URL + String validUrl = "http://www.google.com"; + assertEquals(validUrl, + SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, null, false).toString()); + + // test normal URL with user data but no append + String userData = "encryptedUserData=data"; + assertEquals(validUrl, + SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, userData, false) + .toString()); + + // test normal URL with user data and append + assertEquals(validUrl + "?" + userData, + SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, userData, true).toString()); + + // test normal URL without user data and append + assertEquals(validUrl, + SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, null, true).toString()); } @Test |