Merge "SlicePurchaseActivity handle user data based on contents type" into udc-qpr-dev

This commit is contained in:
Sarah Kim
2023-07-07 21:34:09 +00:00
committed by Android (Google) Code Review
4 changed files with 152 additions and 15 deletions

View File

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

View File

@@ -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;
}
@@ -194,13 +196,40 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
}
/**
* 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;
}

View File

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

View File

@@ -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