Add support for network boost purchase, failure, and expiry
Create a JavaScript interface allowing websites to send responses to
SliceStore
Add support for premium capability purchase, failure, and expiry
Create new result for success but pending network setup
Bug: 245882601
Test: manual test response, purchase, expiry, failure
Test: atest TelephonyManagerReadNonDangerousPermissionTest#testPremiumCapabilities
Change-Id: I51fc674f37679fb0eb1314e7b0939555d0767f87
diff --git a/core/api/current.txt b/core/api/current.txt
index 218d7bd..23ff8e1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -41754,6 +41754,7 @@
field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool";
field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY = "premium_capability_maximum_notification_count_int_array";
+ field public static final String KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG = "premium_capability_network_setup_time_millis_long";
field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = "premium_capability_notification_backoff_hysteresis_time_millis_long";
field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG = "premium_capability_notification_display_timeout_millis_long";
field public static final String KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = "premium_capability_purchase_condition_backoff_hysteresis_time_millis_long";
@@ -44048,6 +44049,7 @@
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12; // 0xc
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA = 14; // 0xe
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN = 5; // 0x5
+ field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15; // 0xf
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED = 11; // 0xb
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS = 1; // 0x1
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2; // 0x2
diff --git a/packages/CarrierDefaultApp/Android.bp b/packages/CarrierDefaultApp/Android.bp
index 6990ad0..62ffe38 100644
--- a/packages/CarrierDefaultApp/Android.bp
+++ b/packages/CarrierDefaultApp/Android.bp
@@ -13,4 +13,9 @@
libs: ["SliceStore"],
platform_apis: true,
certificate: "platform",
+ optimize: {
+ proguard_flags_files: [
+ "proguard.flags",
+ ],
+ },
}
diff --git a/packages/CarrierDefaultApp/assets/slice_store_test.html b/packages/CarrierDefaultApp/assets/slice_store_test.html
new file mode 100644
index 0000000..7ddbd2d
--- /dev/null
+++ b/packages/CarrierDefaultApp/assets/slice_store_test.html
@@ -0,0 +1,78 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="description" content="
+ This is a HTML page that calls and verifies responses from the @JavascriptInterface functions of
+ SliceStoreWebInterface. Test SliceStore APIs using ADB shell commands and the APIs below:
+
+ FROM TERMINAL:
+ Allow device to override carrier configs:
+ $ adb root
+ Set PREMIUM_CAPABILITY_PRIORITIZE_LATENCY enabled:
+ $ adb shell cmd phone cc set-value -p supported_premium_capabilities_int_array 34
+ Set the carrier purchase URL to this test HTML file:
+ $ adb shell cmd phone cc set-value -p premium_capability_purchase_url_string \
+ file:///android_asset/slice_store_test.html
+ OPTIONAL: Allow premium capability purchase on LTE:
+ $ adb shell cmd phone cc set-value -p premium_capability_supported_on_lte_bool true
+ OPTIONAL: Override ServiceState to fake a NR SA connection:
+ $ adb shell am broadcast -a com.android.internal.telephony.TestServiceState --ei data_rat 20
+
+ FROM TEST ACTIVITY:
+ TelephonyManager tm = getApplicationContext().getSystemService(TelephonyManager.class)
+ tm.isPremiumCapabilityAvailable(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+ LinkedBlockingQueue<Integer> purchaseRequests = new LinkedBlockingQueue<>();
+ tm.purchasePremiumCapability(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY,
+ this.getMainExecutor(), request::offer);
+
+ When the test application starts, this HTML will be loaded into the WebView along with the
+ associated JavaScript functions in file:///android_asset/slice_store_test.js.
+ Click on the buttons in the HTML to call the corresponding @JavascriptInterface APIs.
+
+ RESET DEVICE STATE:
+ Clear carrier configurations that were set:
+ $ adb shell cmd phone cc clear-values
+ Clear ServiceState override that was set:
+ $ adb shell am broadcast -a com.android.internal.telephony.TestServiceState --es action reset
+ ">
+ <title>Test SliceStoreActivity</title>
+ <script type="text/javascript" src="slice_store_test.js"></script>
+</head>
+<body>
+ <h1>Test SliceStoreActivity</h1>
+ <h2>Get requested premium capability</h2>
+ <button type="button" onclick="testGetRequestedCapability()">
+ Get requested premium capability
+ </button>
+ <p id="requested_capability"></p>
+
+ <h2>Notify purchase successful</h2>
+ <button type="button" onclick="testNotifyPurchaseSuccessful(60000)">
+ Notify purchase successful for 1 minute
+ </button>
+ <p id="purchase_successful"></p>
+
+ <h2>Notify purchase failed</h2>
+ <button type="button" onclick="testNotifyPurchaseFailed()">
+ Notify purchase failed
+ </button>
+ <p id="purchase_failed"></p>
+</body>
+</html>
diff --git a/packages/CarrierDefaultApp/assets/slice_store_test.js b/packages/CarrierDefaultApp/assets/slice_store_test.js
new file mode 100644
index 0000000..f12a6da
--- /dev/null
+++ b/packages/CarrierDefaultApp/assets/slice_store_test.js
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+function testGetRequestedCapability() {
+ let capability = SliceStoreWebInterface.getRequestedCapability();
+ document.getElementById("requested_capability").innerHTML =
+ "Premium capability requested: " + capability;
+}
+
+function testNotifyPurchaseSuccessful(duration_ms_long = 0) {
+ SliceStoreWebInterface.notifyPurchaseSuccessful(duration);
+ document.getElementById("purchase_successful").innerHTML =
+ "Notified purchase success for duration: " + duration;
+}
+
+function testNotifyPurchaseFailed() {
+ SliceStoreWebInterface.notifyPurchaseFailed();
+ document.getElementById("purchase_failed").innerHTML =
+ "Notified purchase failed.";
+}
diff --git a/packages/CarrierDefaultApp/proguard.flags b/packages/CarrierDefaultApp/proguard.flags
new file mode 100644
index 0000000..64fec2c
--- /dev/null
+++ b/packages/CarrierDefaultApp/proguard.flags
@@ -0,0 +1,4 @@
+# Keep classes and methods that have the @JavascriptInterface annotation
+-keepclassmembers class * {
+ @android.webkit.JavascriptInterface <methods>;
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
index 602e31c..348e389 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
@@ -20,47 +20,63 @@
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.WebView;
import com.android.phone.slicestore.SliceStore;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.concurrent.TimeUnit;
/**
* Activity that launches when the user clicks on the network boost notification.
+ * This will open a {@link WebView} for the carrier website to allow the user to complete the
+ * premium capability purchase.
+ * The carrier website can get the requested premium capability using the JavaScript interface
+ * method {@code SliceStoreWebInterface.getRequestedCapability()}.
+ * If the purchase is successful, the carrier website shall notify SliceStore using the JavaScript
+ * interface method {@code SliceStoreWebInterface.notifyPurchaseSuccessful(duration)}, where
+ * {@code duration} is the duration of the network boost.
+ * If the purchase was not successful, the carrier website shall notify SliceStore using the
+ * JavaScript interface method {@code SliceStoreWebInterface.notifyPurchaseFailed()}.
+ * If either of these notification methods are not called, the purchase cannot be completed
+ * successfully and the purchase request will eventually time out.
*/
public class SliceStoreActivity extends Activity {
private static final String TAG = "SliceStoreActivity";
- private URL mUrl;
- private WebView mWebView;
- private int mPhoneId;
+ private @NonNull WebView mWebView;
+ private @NonNull Context mApplicationContext;
private int mSubId;
- private @TelephonyManager.PremiumCapability int mCapability;
+ @TelephonyManager.PremiumCapability protected int mCapability;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
- mPhoneId = intent.getIntExtra(SliceStore.EXTRA_PHONE_ID,
- SubscriptionManager.INVALID_PHONE_INDEX);
mSubId = intent.getIntExtra(SliceStore.EXTRA_SUB_ID,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mCapability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
SliceStore.PREMIUM_CAPABILITY_INVALID);
- mUrl = getUrl();
- logd("onCreate: mPhoneId=" + mPhoneId + ", mSubId=" + mSubId + ", mCapability="
+ mApplicationContext = getApplicationContext();
+ URL url = getUrl();
+ logd("onCreate: subId=" + mSubId + ", capability="
+ TelephonyManager.convertPremiumCapabilityToString(mCapability)
- + ", mUrl=" + mUrl);
- getApplicationContext().getSystemService(NotificationManager.class)
+ + ", url=" + url);
+
+ // Cancel network boost notification
+ mApplicationContext.getSystemService(NotificationManager.class)
.cancel(SliceStoreBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability);
+
+ // Verify intent and values are valid
if (!SliceStoreBroadcastReceiver.isIntentValid(intent)) {
loge("Not starting SliceStoreActivity with an invalid Intent: " + intent);
SliceStoreBroadcastReceiver.sendSliceStoreResponse(
@@ -68,10 +84,15 @@
finishAndRemoveTask();
return;
}
- if (mUrl == null) {
- loge("Unable to create a URL from carrier configs.");
- SliceStoreBroadcastReceiver.sendSliceStoreResponse(
- intent, SliceStore.EXTRA_INTENT_CARRIER_ERROR);
+ if (url == null) {
+ String error = "Unable to create a URL from carrier configs.";
+ loge(error);
+ Intent data = new Intent();
+ data.putExtra(SliceStore.EXTRA_FAILURE_CODE,
+ SliceStore.FAILURE_CODE_CARRIER_URL_UNAVAILABLE);
+ data.putExtra(SliceStore.EXTRA_FAILURE_REASON, error);
+ SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData(
+ mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_CARRIER_ERROR, data);
finishAndRemoveTask();
return;
}
@@ -83,12 +104,53 @@
return;
}
+ // Create a reference to this activity in SliceStoreBroadcastReceiver
SliceStoreBroadcastReceiver.updateSliceStoreActivity(mCapability, this);
+ // Create and configure WebView
mWebView = new WebView(this);
+ // Enable JavaScript for the carrier purchase website to send results back to SliceStore
+ mWebView.getSettings().setJavaScriptEnabled(true);
+ mWebView.addJavascriptInterface(new SliceStoreWebInterface(this), "SliceStoreWebInterface");
+
+ // Display WebView
setContentView(mWebView);
- mWebView.loadUrl(mUrl.toString());
- // TODO(b/245882601): Get back response from WebView
+ mWebView.loadUrl(url.toString());
+ }
+
+ protected void onPurchaseSuccessful(long duration) {
+ logd("onPurchaseSuccessful: Carrier website indicated successfully purchased premium "
+ + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability)
+ + " for " + TimeUnit.MILLISECONDS.toMinutes(duration) + " minutes.");
+ Intent intent = new Intent();
+ intent.putExtra(SliceStore.EXTRA_PURCHASE_DURATION, duration);
+ SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData(
+ mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_SUCCESS, intent);
+ finishAndRemoveTask();
+ }
+
+ protected void onPurchaseFailed(@SliceStore.FailureCode int failureCode,
+ @Nullable String failureReason) {
+ logd("onPurchaseFailed: Carrier website indicated purchase failed for premium capability "
+ + TelephonyManager.convertPremiumCapabilityToString(mCapability) + " with code: "
+ + SliceStore.convertFailureCodeToString(failureCode) + " and reason: "
+ + failureReason);
+ Intent data = new Intent();
+ data.putExtra(SliceStore.EXTRA_FAILURE_CODE, failureCode);
+ data.putExtra(SliceStore.EXTRA_FAILURE_REASON, failureReason);
+ SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData(
+ mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_CARRIER_ERROR, data);
+ finishAndRemoveTask();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
+ // Pressing back in the WebView will go to the previous page instead of closing SliceStore.
+ if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {
+ mWebView.goBack();
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
}
@Override
@@ -100,8 +162,8 @@
super.onDestroy();
}
- private @Nullable URL getUrl() {
- String url = getApplicationContext().getSystemService(CarrierConfigManager.class)
+ @Nullable private URL getUrl() {
+ String url = mApplicationContext.getSystemService(CarrierConfigManager.class)
.getConfigForSubId(mSubId).getString(
CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
try {
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
index 7eb851d..7867ef1 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
@@ -26,6 +26,7 @@
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
+import android.telephony.AnomalyReporter;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -37,6 +38,7 @@
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
+import java.util.UUID;
/**
* The SliceStoreBroadcastReceiver listens for {@link SliceStore#ACTION_START_SLICE_STORE} from the
@@ -47,6 +49,12 @@
public class SliceStoreBroadcastReceiver extends BroadcastReceiver{
private static final String TAG = "SliceStoreBroadcastReceiver";
+ /**
+ * UUID to report an anomaly when receiving a PendingIntent from an application or process
+ * other than the Phone process.
+ */
+ private static final String UUID_BAD_PENDING_INTENT = "c360246e-95dc-4abf-9dc1-929a76cd7e53";
+
/** Weak references to {@link SliceStoreActivity} for each capability, if it exists. */
private static final Map<Integer, WeakReference<SliceStoreActivity>> sSliceStoreActivities =
new HashMap<>();
@@ -102,6 +110,28 @@
}
/**
+ * Send the PendingIntent containing the corresponding SliceStore response with additional data.
+ *
+ * @param context The Context to use to send the PendingIntent.
+ * @param intent The Intent containing the PendingIntent extra.
+ * @param extra The extra to get the PendingIntent to send.
+ * @param data The Intent containing additional data to send with the PendingIntent.
+ */
+ public static void sendSliceStoreResponseWithData(@NonNull Context context,
+ @NonNull Intent intent, @NonNull String extra, @NonNull Intent data) {
+ PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class);
+ if (pendingIntent == null) {
+ loge("PendingIntent does not exist for extra: " + extra);
+ return;
+ }
+ try {
+ pendingIntent.send(context, 0 /* unused */, data);
+ } catch (PendingIntent.CanceledException e) {
+ loge("Unable to send " + getPendingIntentType(extra) + " intent: " + e);
+ }
+ }
+
+ /**
* Check whether the Intent is valid and can be used to complete purchases in the SliceStore.
* This checks that all necessary extras exist and that the values are valid.
*
@@ -139,7 +169,8 @@
return isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_CANCELED)
&& isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_CARRIER_ERROR)
&& isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_REQUEST_FAILED)
- && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA);
+ && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA)
+ && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_SUCCESS);
}
private static boolean isPendingIntentValid(@NonNull Intent intent, @NonNull String extra) {
@@ -148,12 +179,20 @@
if (pendingIntent == null) {
loge("isPendingIntentValid: " + intentType + " intent not found.");
return false;
- } else if (pendingIntent.getCreatorPackage().equals(TelephonyManager.PHONE_PROCESS_NAME)) {
- return true;
}
- loge("isPendingIntentValid: " + intentType + " intent was created by "
- + pendingIntent.getCreatorPackage() + " instead of the phone process.");
- return false;
+ String creatorPackage = pendingIntent.getCreatorPackage();
+ if (!creatorPackage.equals(TelephonyManager.PHONE_PROCESS_NAME)) {
+ String logStr = "isPendingIntentValid: " + intentType + " intent was created by "
+ + creatorPackage + " instead of the phone process.";
+ loge(logStr);
+ AnomalyReporter.reportAnomaly(UUID.fromString(UUID_BAD_PENDING_INTENT), logStr);
+ return false;
+ }
+ if (!pendingIntent.isBroadcast()) {
+ loge("isPendingIntentValid: " + intentType + " intent is not a broadcast.");
+ return false;
+ }
+ return true;
}
@NonNull private static String getPendingIntentType(@NonNull String extra) {
@@ -162,6 +201,7 @@
case SliceStore.EXTRA_INTENT_CARRIER_ERROR: return "carrier error";
case SliceStore.EXTRA_INTENT_REQUEST_FAILED: return "request failed";
case SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA: return "not default data";
+ case SliceStore.EXTRA_INTENT_SUCCESS: return "success";
default: {
loge("Unknown pending intent extra: " + extra);
return "unknown(" + extra + ")";
@@ -292,7 +332,6 @@
logd("Closing SliceStore WebView since the user did not complete the purchase "
+ "in time.");
sSliceStoreActivities.get(capability).get().finishAndRemoveTask();
- // TODO: Display a toast to indicate timeout for better UX?
}
}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java
new file mode 100644
index 0000000..ab5d080
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.carrierdefaultapp;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.telephony.TelephonyManager;
+import android.webkit.JavascriptInterface;
+
+import com.android.phone.slicestore.SliceStore;
+
+/**
+ * SliceStore web interface class allowing carrier websites to send responses back to SliceStore
+ * using JavaScript.
+ */
+public class SliceStoreWebInterface {
+ @NonNull SliceStoreActivity mActivity;
+
+ public SliceStoreWebInterface(@NonNull SliceStoreActivity activity) {
+ mActivity = activity;
+ }
+ /**
+ * Interface method allowing the carrier website to get the premium capability
+ * that was requested to purchase.
+ *
+ * This can be called using the JavaScript below:
+ * <script type="text/javascript">
+ * function getRequestedCapability(duration) {
+ * SliceStoreWebInterface.getRequestedCapability();
+ * }
+ * </script>
+ */
+ @JavascriptInterface
+ @TelephonyManager.PremiumCapability public int getRequestedCapability() {
+ return mActivity.mCapability;
+ }
+
+ /**
+ * Interface method allowing the carrier website to notify the SliceStore of a successful
+ * premium capability purchase and the duration for which the premium capability is purchased.
+ *
+ * This can be called using the JavaScript below:
+ * <script type="text/javascript">
+ * function notifyPurchaseSuccessful(duration_ms_long = 0) {
+ * SliceStoreWebInterface.notifyPurchaseSuccessful(duration_ms_long);
+ * }
+ * </script>
+ *
+ * @param duration The duration for which the premium capability is purchased in milliseconds.
+ */
+ @JavascriptInterface
+ public void notifyPurchaseSuccessful(long duration) {
+ mActivity.onPurchaseSuccessful(duration);
+ }
+
+ /**
+ * Interface method allowing the carrier website to notify the SliceStore of a failed
+ * premium capability purchase.
+ *
+ * This can be called using the JavaScript below:
+ * <script type="text/javascript">
+ * function notifyPurchaseFailed() {
+ * SliceStoreWebInterface.notifyPurchaseFailed();
+ * }
+ * </script>
+ *
+ * @param failureCode The failure code.
+ * @param failureReason If the failure code is {@link SliceStore#FAILURE_CODE_UNKNOWN},
+ * the human-readable reason for failure.
+ */
+ @JavascriptInterface
+ public void notifyPurchaseFailed(@SliceStore.FailureCode int failureCode,
+ @Nullable String failureReason) {
+ mActivity.onPurchaseFailed(failureCode, failureReason);
+ }
+}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d314a65..246488e 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8758,6 +8758,22 @@
"premium_capability_purchase_condition_backoff_hysteresis_time_millis_long";
/**
+ * The amount of time in milliseconds within which the network must set up a slicing
+ * configuration for the premium capability after
+ * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
+ * returns {@link TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS}.
+ * During the setup time, calls to
+ * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)} will return
+ * {@link TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP}.
+ * If the network fails set up a slicing configuration for the premium capability within the
+ * setup time, subsequent purchase requests will be allowed to go through again.
+ *
+ * The default value is 5 minutes.
+ */
+ public static final String KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG =
+ "premium_capability_network_setup_time_millis_long";
+
+ /**
* The URL to redirect to when the user clicks on the notification for a network boost via
* premium capabilities after applications call
* {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}.
@@ -9474,6 +9490,8 @@
sDefaults.putLong(
KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
TimeUnit.MINUTES.toMillis(30));
+ sDefaults.putLong(KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG,
+ TimeUnit.MINUTES.toMillis(5));
sDefaults.putString(KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING, null);
sDefaults.putBoolean(KEY_PREMIUM_CAPABILITY_SUPPORTED_ON_LTE_BOOL, false);
sDefaults.putStringArray(KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY, new String[]{
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ff1b1c0..d8e5fa2 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17195,7 +17195,13 @@
}
/**
- * Purchase premium capability request was successful. Subsequent attempts will return
+ * Purchase premium capability request was successful.
+ * Once the purchase result is successful, the network must set up a slicing configuration
+ * for the purchased premium capability within the timeout specified by
+ * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG}.
+ * During the setup time, subsequent attempts will return
+ * {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP}.
+ * After setup is complete, subsequent attempts will return
* {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED} until the booster expires.
* The expiry time is determined by the type or duration of boost purchased from the carrier,
* provided at {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING}.
@@ -17319,6 +17325,16 @@
public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA = 14;
/**
+ * Purchase premium capability was successful and is waiting for the network to setup the
+ * slicing configuration. If the setup is complete within the time specified by
+ * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG},
+ * subsequent requests will return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED}
+ * until the purchase expires. If the setup is not complete within the time specified above,
+ * applications can reques the premium capability again.
+ */
+ public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15;
+
+ /**
* Results of the purchase premium capability request.
* @hide
*/
@@ -17336,7 +17352,8 @@
PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED,
PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED,
- PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA})
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA,
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP})
public @interface PurchasePremiumCapabilityResult {}
/**
@@ -17377,6 +17394,8 @@
return "NETWORK_CONGESTED";
case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA:
return "NOT_DEFAULT_DATA";
+ case PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP:
+ return "PENDING_NETWORK_SETUP";
default:
return "UNKNOWN (" + result + ")";
}