diff options
10 files changed, 377 insertions, 27 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 218d7bd14ceb..23ff8e109a1b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -41754,6 +41754,7 @@ package android.telephony { 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 @@ package android.telephony { 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 6990ad0fbd7d..62ffe3898667 100644 --- a/packages/CarrierDefaultApp/Android.bp +++ b/packages/CarrierDefaultApp/Android.bp @@ -13,4 +13,9 @@ android_app { 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 000000000000..7ddbd2d5f245 --- /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 000000000000..f12a6daf8de3 --- /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 000000000000..64fec2ccbef2 --- /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 602e31cbd130..348e3895690c 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.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.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 @@ public class SliceStoreActivity extends Activity { 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 @@ public class SliceStoreActivity extends Activity { 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 @@ public class SliceStoreActivity extends Activity { 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 7eb851dcdd58..7867ef1c6516 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.Context; 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 com.android.phone.slicestore.SliceStore; 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 @@ import java.util.Map; 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 @@ public class SliceStoreBroadcastReceiver extends BroadcastReceiver{ } /** + * 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 @@ public class SliceStoreBroadcastReceiver extends BroadcastReceiver{ 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 @@ public class SliceStoreBroadcastReceiver extends BroadcastReceiver{ 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 @@ public class SliceStoreBroadcastReceiver extends BroadcastReceiver{ 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 @@ public class SliceStoreBroadcastReceiver extends BroadcastReceiver{ 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 000000000000..ab5d0809a1f6 --- /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 d314a6579b58..246488e4aeaa 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -8758,6 +8758,22 @@ public class CarrierConfigManager { "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 @@ public class CarrierConfigManager { 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 ff1b1c097d68..d8e5fa2161a7 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -17195,7 +17195,13 @@ public class TelephonyManager { } /** - * 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 class TelephonyManager { 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 @@ public class TelephonyManager { 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 @@ public class TelephonyManager { 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 + ")"; } |