Create notification for premium capability purchase
Test: manual test to cancel/timeout/accept/delay/manage
Test: atest TelephonyManagerTest#testPremiumCapabilities
Bug: 245882092
Change-Id: I34b4b549d78a68be15b6d14c8f7f494fdc091385
diff --git a/res/drawable/ic_network_boost.xml b/res/drawable/ic_network_boost.xml
new file mode 100644
index 0000000..b6a7ae1
--- /dev/null
+++ b/res/drawable/ic_network_boost.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+<path android:fillColor="@android:color/white"
+ android:pathData="M3,17V15H8Q8,15 8,15Q8,15 8,15V13Q8,13 8,13Q8,13 8,13H3V7H10V9H5V11H8Q8.825,11 9.413,11.587Q10,12.175 10,13V15Q10,15.825 9.413,16.413Q8.825,17 8,17ZM21,11V15Q21,15.825 20.413,16.413Q19.825,17 19,17H14Q13.175,17 12.588,16.413Q12,15.825 12,15V9Q12,8.175 12.588,7.587Q13.175,7 14,7H19Q19.825,7 20.413,7.587Q21,8.175 21,9H14Q14,9 14,9Q14,9 14,9V15Q14,15 14,15Q14,15 14,15H19Q19,15 19,15Q19,15 19,15V13H16.5V11Z"/>
+</vector>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 38a86f9..53ca126 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2198,4 +2198,15 @@
<!-- Telephony notification channel name for a channel containing SIP accounts removed
notificatios -->
<string name="notification_channel_sip_account">Deprecated SIP accounts</string>
+
+ <!-- Telephony notification channel name for network boost notifications. -->
+ <string name="network_boost_notification_channel">Network Boost</string>
+ <!-- Notification title text for the network boost notification. -->
+ <string name="network_boost_notification_title">%s recommends a data boost</string>
+ <!-- Notification detail text for the network boost notification. -->
+ <string name="network_boost_notification_detail">Buy a network boost for better performance</string>
+ <!-- Notification button text to delay the network boost notification. -->
+ <string name="network_boost_notification_button_delay">Not now</string>
+ <!-- Notification button text to manage the network boost notification. -->
+ <string name="network_boost_notification_button_manage">Manage</string>
</resources>
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 5699ca8..23390cf 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -472,6 +472,19 @@
}
}
+ private static final class PurchasePremiumCapabilityArgument {
+ public @TelephonyManager.PremiumCapability int capability;
+ public @NonNull String appName;
+ public @NonNull IIntegerConsumer callback;
+
+ PurchasePremiumCapabilityArgument(@TelephonyManager.PremiumCapability int capability,
+ @NonNull String appName, @NonNull IIntegerConsumer callback) {
+ this.capability = capability;
+ this.appName = appName;
+ this.callback = callback;
+ }
+ }
+
/**
* A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
* request after sending. The main thread will notify the request when it is complete.
@@ -2140,35 +2153,38 @@
break;
}
- case CMD_PURCHASE_PREMIUM_CAPABILITY:
+ case CMD_PURCHASE_PREMIUM_CAPABILITY: {
request = (MainThreadRequest) msg.obj;
onCompleted = obtainMessage(EVENT_PURCHASE_PREMIUM_CAPABILITY_DONE, request);
+ PurchasePremiumCapabilityArgument arg =
+ (PurchasePremiumCapabilityArgument) request.argument;
SliceStore.getInstance(request.phone).purchasePremiumCapability(
- ((Pair<Integer, IIntegerConsumer>) request.argument).first,
- onCompleted);
+ arg.capability, arg.appName, onCompleted);
break;
+ }
- case EVENT_PURCHASE_PREMIUM_CAPABILITY_DONE:
+ case EVENT_PURCHASE_PREMIUM_CAPABILITY_DONE: {
ar = (AsyncResult) msg.obj;
request = (MainThreadRequest) ar.userObj;
- Pair<Integer, IIntegerConsumer> pair =
- (Pair<Integer, IIntegerConsumer>) request.argument;
+ PurchasePremiumCapabilityArgument arg =
+ (PurchasePremiumCapabilityArgument) request.argument;
try {
int result = (int) ar.result;
- pair.second.accept(result);
+ arg.callback.accept(result);
log("purchasePremiumCapability: capability="
- + TelephonyManager.convertPremiumCapabilityToString(pair.first)
+ + TelephonyManager.convertPremiumCapabilityToString(arg.capability)
+ ", result= "
+ TelephonyManager.convertPurchaseResultToString(result));
} catch (RemoteException e) {
String logStr = "Purchase premium capability "
- + TelephonyManager.convertPremiumCapabilityToString(pair.first)
+ + TelephonyManager.convertPremiumCapabilityToString(arg.capability)
+ " failed: " + e;
if (DBG) log(logStr);
AnomalyReporter.reportAnomaly(
UUID.fromString(PURCHASE_PREMIUM_CAPABILITY_ERROR_UUID), logStr);
}
break;
+ }
case CMD_PREPARE_UNATTENDED_REBOOT:
request = (MainThreadRequest) msg.obj;
@@ -11274,8 +11290,15 @@
}
Phone phone = getPhone(subId);
- Pair<Integer, IIntegerConsumer> argument = new Pair<>(capability, callback);
- sendRequestAsync(CMD_PURCHASE_PREMIUM_CAPABILITY, argument, phone, null);
+ String appName;
+ try {
+ appName = mApp.getPackageManager().getApplicationLabel(mApp.getPackageManager()
+ .getApplicationInfo(getCurrentPackageName(), 0)).toString();
+ } catch (PackageManager.NameNotFoundException e) {
+ appName = "An application";
+ }
+ sendRequestAsync(CMD_PURCHASE_PREMIUM_CAPABILITY,
+ new PurchasePremiumCapabilityArgument(capability, appName, callback), phone, null);
}
/**
diff --git a/src/com/android/phone/slicestore/SliceStore.java b/src/com/android/phone/slicestore/SliceStore.java
index aa564c8..4bf6874 100644
--- a/src/com/android/phone/slicestore/SliceStore.java
+++ b/src/com/android/phone/slicestore/SliceStore.java
@@ -18,6 +18,15 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.drawable.Icon;
import android.net.ConnectivityManager;
import android.os.AsyncResult;
import android.os.Handler;
@@ -26,19 +35,24 @@
import android.os.PersistableBundle;
import android.telephony.AnomalyReporter;
import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.data.NetworkSliceInfo;
import android.telephony.data.NetworkSlicingConfig;
import android.util.Log;
-import android.util.SparseBooleanArray;
+import android.webkit.WebView;
import com.android.internal.telephony.Phone;
+import com.android.phone.R;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
@@ -48,10 +62,9 @@
* they can then call {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
* to purchase the premium capability. If all conditions are met, a notification will be displayed
* to the user prompting them to purchase the premium capability. If the user confirms on the
- * notification, a (TODO: add link) WebView will open that allows the user to purchase the
- * premium capability from the carrier. If the purchase is successful, the premium capability
- * will be available for all applications to request through
- * {@link ConnectivityManager#requestNetwork}.
+ * notification, a {@link WebView} will open that allows the user to purchase the premium capability
+ * from the carrier. If the purchase is successful, the premium capability will be available for
+ * all applications to request through {@link ConnectivityManager#requestNetwork}.
*/
public class SliceStore extends Handler {
@NonNull private static final String TAG = "SliceStore";
@@ -67,22 +80,104 @@
/** UUID to report an anomaly when a premium capability is throttled twice in a row. */
private static final String UUID_CAPABILITY_THROTTLED_TWICE =
"15574927-e2e2-4593-99d4-2f340d22b383";
+ /** UUID to report an anomaly when the BroadcastReceiver receives an invalid phone ID. */
+ private static final String UUID_INVALID_PHONE_ID = "ced79f1a-8ac0-4260-8cf3-08b54c0494f3";
+ /** UUID to report an anomaly when the BroadcastReceiver receives an unknown action. */
+ private static final String UUID_UNKNOWN_ACTION = "0197efb0-dab1-4b0a-abaf-ac9336ec7923";
- /** Map of phone ID -> SliceStore. */
+ /** Channel ID for the network boost notification. */
+ private static final String NETWORK_BOOST_NOTIFICATION_CHANNEL_ID = "network_boost";
+ /** Tag for the network boost notification. */
+ private static final String NETWORK_BOOST_NOTIFICATION_TAG = "SliceStore.Notification";
+
+ /** Action for when the network boost notification is cancelled. */
+ private static final String ACTION_NOTIFICATION_CANCELED =
+ "com.android.phone.slicestore.action.NOTIFICATION_CANCELED";
+ /** Action for when the user clicks the "Not now" button on the network boost notification. */
+ private static final String ACTION_NOTIFICATION_DELAYED =
+ "com.android.phone.slicestore.action.NOTIFICATION_DELAYED";
+ /** Action for when the user clicks the "Manage" button on the network boost notification. */
+ private static final String ACTION_NOTIFICATION_MANAGE =
+ "com.android.phone.slicestore.action.NOTIFICATION_MANAGE";
+ /** Extra for phone ID to send from the network boost notification. */
+ private static final String EXTRA_PHONE_ID = "com.android.phone.slicestore.extra.PHONE_ID";
+ /** Extra for premium capability to send from the network boost notification. */
+ private static final String EXTRA_PREMIUM_CAPABILITY =
+ "com.android.phone.slicestore.extra.PREMIUM_CAPABILITY";
+
+ /** Map of phone ID -> SliceStore instances. */
@NonNull private static final Map<Integer, SliceStore> sInstances = new HashMap<>();
+ /** The Phone instance used to create the SliceStore */
@NonNull private final Phone mPhone;
- @NonNull private final SparseBooleanArray mPurchasedCapabilities = new SparseBooleanArray();
- @NonNull private final SparseBooleanArray mThrottledCapabilities = new SparseBooleanArray();
- @NonNull private final SparseBooleanArray mPendingPurchaseCapabilities =
- new SparseBooleanArray();
+ /** The set of purchased capabilities. */
+ @NonNull private final Set<Integer> mPurchasedCapabilities = new HashSet<>();
+ /** The set of throttled capabilities. */
+ @NonNull private final Set<Integer> mThrottledCapabilities = new HashSet<>();
+ /** A map of pending capabilities to the onComplete message for the purchase request. */
+ @NonNull private final Map<Integer, Message> mPendingPurchaseCapabilities = new HashMap<>();
+ /** A map of capabilities to the CapabilityBroadcastReceiver for the boost notification. */
+ @NonNull private final Map<Integer, CapabilityBroadcastReceiver> mBroadcastReceivers =
+ new HashMap<>();
+ /** The current network slicing configuration. */
@Nullable private NetworkSlicingConfig mSlicingConfig;
+ private final class CapabilityBroadcastReceiver extends BroadcastReceiver {
+ @TelephonyManager.PremiumCapability final int mCapability;
+
+ CapabilityBroadcastReceiver(@TelephonyManager.PremiumCapability int capability) {
+ mCapability = capability;
+ }
+
+ @Override
+ public void onReceive(@NonNull Context context, @NonNull Intent intent) {
+ String action = intent.getAction();
+ log("CapabilityBroadcastReceiver("
+ + TelephonyManager.convertPremiumCapabilityToString(mCapability)
+ + ") received action: " + action);
+ int phoneId = intent.getIntExtra(EXTRA_PHONE_ID,
+ SubscriptionManager.INVALID_PHONE_INDEX);
+ int capability = intent.getIntExtra(EXTRA_PREMIUM_CAPABILITY, -1);
+ if (SliceStore.getInstance(phoneId) == null) {
+ String logStr = "CapabilityBroadcastReceiver( "
+ + TelephonyManager.convertPremiumCapabilityToString(mCapability)
+ + ") received invalid phoneId: " + phoneId;
+ loge(logStr);
+ AnomalyReporter.reportAnomaly(UUID.fromString(UUID_INVALID_PHONE_ID), logStr);
+ return;
+ } else if (capability != mCapability) {
+ log("CapabilityBroadcastReceiver("
+ + TelephonyManager.convertPremiumCapabilityToString(mCapability)
+ + ") received invalid capability: "
+ + TelephonyManager.convertPremiumCapabilityToString(capability));
+ return;
+ }
+ switch (action) {
+ case ACTION_NOTIFICATION_CANCELED:
+ SliceStore.getInstance(phoneId).onUserCanceled(capability);
+ break;
+ case ACTION_NOTIFICATION_DELAYED:
+ SliceStore.getInstance(phoneId).onUserDelayed(capability);
+ break;
+ case ACTION_NOTIFICATION_MANAGE:
+ SliceStore.getInstance(phoneId).onUserManage(capability);
+ break;
+ default:
+ String logStr = "CapabilityBroadcastReceiver("
+ + TelephonyManager.convertPremiumCapabilityToString(mCapability)
+ + ") received unknown action: " + action;
+ loge(logStr);
+ AnomalyReporter.reportAnomaly(UUID.fromString(UUID_UNKNOWN_ACTION), logStr);
+ break;
+ }
+ }
+ }
+
/**
- * Get the static SliceStore instance for the given phone.
+ * Get the static SliceStore instance for the given phone or create one if it doesn't exist.
*
- * @param phone The phone to get the SliceStore for
- * @return The static SliceStore instance
+ * @param phone The Phone to get the SliceStore for.
+ * @return The static SliceStore instance.
*/
@NonNull public static synchronized SliceStore getInstance(@NonNull Phone phone) {
// TODO: Add listeners for multi sim setting changed (maybe carrier config changed too)
@@ -94,6 +189,16 @@
return sInstances.get(phoneId);
}
+ /**
+ * Get the static SliceStore instance for the given phone ID if it exists.
+ *
+ * @param phoneId The phone ID to get the SliceStore for.
+ * @return The static SliceStore instance or {@code null} if it hasn't been created yet.
+ */
+ @Nullable private static SliceStore getInstance(int phoneId) {
+ return sInstances.get(phoneId);
+ }
+
private SliceStore(@NonNull Phone phone) {
super(Looper.myLooper());
mPhone = phone;
@@ -108,7 +213,7 @@
int capability = (int) msg.obj;
log("EVENT_PURCHASE_UNTHROTTLED: for capability "
+ TelephonyManager.convertPremiumCapabilityToString(capability));
- mThrottledCapabilities.setValueAt(capability, false);
+ mThrottledCapabilities.remove(capability);
break;
}
case EVENT_SLICING_CONFIG_CHANGED: {
@@ -119,14 +224,18 @@
break;
}
case EVENT_DISPLAY_BOOSTER_NOTIFICATION: {
- onDisplayBoosterNotification(msg.arg1, (Message) msg.obj);
+ int capability = msg.arg1;
+ String appName = (String) msg.obj;
+ log("EVENT_DISPLAY_BOOSTER_NOTIFICATION: " + appName + " requests capability "
+ + TelephonyManager.convertPremiumCapabilityToString(capability));
+ onDisplayBoosterNotification(capability, appName);
break;
}
case EVENT_PURCHASE_TIMEOUT: {
- int capability = msg.arg1;
+ int capability = (int) msg.obj;
log("EVENT_PURCHASE_TIMEOUT: for capability "
+ TelephonyManager.convertPremiumCapabilityToString(capability));
- onTimeout(capability, (Message) msg.obj);
+ onTimeout(capability);
break;
}
}
@@ -164,11 +273,13 @@
* Purchase the given premium capability from the carrier.
*
* @param capability The premium capability to purchase.
+ * @param appName The name of the application requesting premium capabilities.
* @param onComplete The callback message to send when the purchase request is complete.
*/
public synchronized void purchasePremiumCapability(
- @TelephonyManager.PremiumCapability int capability, @NonNull Message onComplete) {
- log("purchasePremiumCapability: "
+ @TelephonyManager.PremiumCapability int capability, @NonNull String appName,
+ @NonNull Message onComplete) {
+ log("purchasePremiumCapability: " + appName + " requests capability "
+ TelephonyManager.convertPremiumCapabilityToString(capability));
// Check whether the premium capability can be purchased.
if (!arePremiumCapabilitiesSupportedByDevice()) {
@@ -189,13 +300,14 @@
onComplete);
return;
}
- if (mPurchasedCapabilities.get(capability) || isSlicingConfigActive(capability)) {
+ if (mPurchasedCapabilities.contains(capability) || isSlicingConfigActive(capability)) {
+ // TODO (b/245882601): Handle capability expiry
sendPurchaseResult(capability,
TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED,
onComplete);
return;
}
- if (mThrottledCapabilities.get(capability)) {
+ if (mThrottledCapabilities.contains(capability)) {
sendPurchaseResult(capability,
TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED,
onComplete);
@@ -208,13 +320,14 @@
return;
}
if (isNetworkCongested(capability)) {
- throttleCapability(capability);
+ throttleCapability(capability, getThrottleDuration(
+ TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED));
sendPurchaseResult(capability,
TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED,
onComplete);
return;
}
- if (mPendingPurchaseCapabilities.get(capability)) {
+ if (mPendingPurchaseCapabilities.containsKey(capability)) {
sendPurchaseResult(capability,
TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS,
onComplete);
@@ -223,9 +336,9 @@
// All state checks passed. Mark purchase pending and display the booster notification to
// prompt user purchase. Process through the handler since this method is synchronized.
- mPendingPurchaseCapabilities.put(capability, true);
- sendMessage(obtainMessage(EVENT_DISPLAY_BOOSTER_NOTIFICATION,
- capability, 0 /* unused */, onComplete));
+ mPendingPurchaseCapabilities.put(capability, onComplete);
+ sendMessage(obtainMessage(EVENT_DISPLAY_BOOSTER_NOTIFICATION, capability, 0 /* unused */,
+ appName));
}
private void sendPurchaseResult(@TelephonyManager.PremiumCapability int capability,
@@ -239,55 +352,176 @@
onComplete.sendToTarget();
}
- private void throttleCapability(@TelephonyManager.PremiumCapability int capability) {
+ private void throttleCapability(@TelephonyManager.PremiumCapability int capability,
+ long throttleDuration) {
// Throttle subsequent requests if necessary.
- if (!mThrottledCapabilities.get(capability)) {
- long throttleTime = getThrottleDuration(capability);
- if (throttleTime > 0) {
+ if (!mThrottledCapabilities.contains(capability)) {
+ if (throttleDuration > 0) {
log("Throttle purchase requests for capability "
+ TelephonyManager.convertPremiumCapabilityToString(capability) + " for "
- + (throttleTime / 1000) + " seconds.");
- mThrottledCapabilities.setValueAt(capability, true);
+ + TimeUnit.MILLISECONDS.toMinutes(throttleDuration) + " minutes.");
+ mThrottledCapabilities.add(capability);
sendMessageDelayed(obtainMessage(EVENT_PURCHASE_UNTHROTTLED, capability),
- throttleTime);
+ throttleDuration);
}
} else {
String logStr = TelephonyManager.convertPremiumCapabilityToString(capability)
+ " is already throttled.";
- log(logStr);
+ loge(logStr);
AnomalyReporter.reportAnomaly(UUID.fromString(UUID_CAPABILITY_THROTTLED_TWICE), logStr);
}
}
private void onDisplayBoosterNotification(@TelephonyManager.PremiumCapability int capability,
- @NonNull Message onComplete) {
+ @NonNull String appName) {
+ // Start timeout on handler instead of setTimeoutAfter to differentiate cancel and timeout.
long timeout = getCarrierConfigs().getLong(CarrierConfigManager
.KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG);
+ sendMessageDelayed(obtainMessage(EVENT_PURCHASE_TIMEOUT, capability), timeout);
+
log("Display the booster notification for capability "
+ TelephonyManager.convertPremiumCapabilityToString(capability) + " for "
- + (timeout / 1000) + " seconds.");
- sendMessageDelayed(
- obtainMessage(EVENT_PURCHASE_TIMEOUT, capability, 0 /* unused */, onComplete),
- timeout);
- // TODO(b/245882092): Display notification with listener for
- // EVENT_USER_ACTION or EVENT_USER_CANCELED + EVENT_USER_CONFIRMED
+ + TimeUnit.MILLISECONDS.toMinutes(timeout) + " minutes.");
+
+ mPhone.getContext().getSystemService(NotificationManager.class).createNotificationChannel(
+ new NotificationChannel(NETWORK_BOOST_NOTIFICATION_CHANNEL_ID,
+ mPhone.getContext().getResources().getString(
+ R.string.network_boost_notification_channel),
+ NotificationManager.IMPORTANCE_DEFAULT));
+ mBroadcastReceivers.put(capability, new CapabilityBroadcastReceiver(capability));
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_NOTIFICATION_CANCELED);
+ filter.addAction(ACTION_NOTIFICATION_DELAYED);
+ filter.addAction(ACTION_NOTIFICATION_MANAGE);
+ mPhone.getContext().registerReceiver(mBroadcastReceivers.get(capability), filter);
+
+ Notification notification =
+ new Notification.Builder(mPhone.getContext(), NETWORK_BOOST_NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(String.format(mPhone.getContext().getResources().getString(
+ R.string.network_boost_notification_title), appName))
+ .setContentText(mPhone.getContext().getResources().getString(
+ R.string.network_boost_notification_detail))
+ .setSmallIcon(R.drawable.ic_network_boost)
+ .setContentIntent(getContentIntent(capability))
+ .setDeleteIntent(getDeleteIntent(capability))
+ .addAction(new Notification.Action.Builder(
+ Icon.createWithResource(mPhone.getContext(), R.drawable.ic_network_boost),
+ mPhone.getContext().getResources().getString(
+ R.string.network_boost_notification_button_delay),
+ getDelayIntent(capability)).build())
+ .addAction(new Notification.Action.Builder(
+ Icon.createWithResource(mPhone.getContext(), R.drawable.ic_network_boost),
+ mPhone.getContext().getResources().getString(
+ R.string.network_boost_notification_button_manage),
+ getManageIntent(capability)).build())
+ .setAutoCancel(true)
+ .build();
+
+ mPhone.getContext().getSystemService(NotificationManager.class)
+ .notify(NETWORK_BOOST_NOTIFICATION_TAG, capability, notification);
}
- private void closeBoosterNotification(@TelephonyManager.PremiumCapability int capability) {
- // TODO(b/245882092): Close notification; maybe cancel purchase timeout
+ /**
+ * Create the content intent for when the user clicks on the network boost notification.
+ * Ths will start the {@link SliceStoreActivity} and display the {@link android.webkit.WebView}
+ * to purchase the premium capability from the carrier.
+ *
+ * @param capability The premium capability that was requested.
+ * @return The content intent.
+ */
+ @NonNull private PendingIntent getContentIntent(
+ @TelephonyManager.PremiumCapability int capability) {
+ Intent intent = new Intent(mPhone.getContext(), SliceStoreActivity.class);
+ intent.putExtra(EXTRA_PHONE_ID, mPhone.getPhoneId());
+ intent.putExtra(EXTRA_PREMIUM_CAPABILITY, capability);
+ return PendingIntent.getActivity(mPhone.getContext(), 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
}
- private void onTimeout(@TelephonyManager.PremiumCapability int capability,
- @NonNull Message onComplete) {
- closeBoosterNotification(capability);
- mPendingPurchaseCapabilities.put(capability, false);
- throttleCapability(capability);
- sendPurchaseResult(capability, TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT,
- onComplete);
+ /**
+ * Create the delete intent for when the user cancels the network boost notification.
+ * This will send {@link #ACTION_NOTIFICATION_CANCELED}.
+ *
+ * @param capability The premium capability that was requested.
+ * @return The delete intent.
+ */
+ @NonNull private PendingIntent getDeleteIntent(
+ @TelephonyManager.PremiumCapability int capability) {
+ Intent intent = new Intent(ACTION_NOTIFICATION_CANCELED);
+ intent.putExtra(EXTRA_PHONE_ID, mPhone.getPhoneId());
+ intent.putExtra(EXTRA_PREMIUM_CAPABILITY, capability);
+ return PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
+ }
+
+ /**
+ * Create the delay intent for when the user clicks the "Not now" button on the network boost
+ * notification. This will send {@link #ACTION_NOTIFICATION_DELAYED}.
+ *
+ * @param capability The premium capability that was requested.
+ * @return The delay intent.
+ */
+ @NonNull private PendingIntent getDelayIntent(
+ @TelephonyManager.PremiumCapability int capability) {
+ Intent intent = new Intent(ACTION_NOTIFICATION_DELAYED);
+ intent.putExtra(EXTRA_PHONE_ID, mPhone.getPhoneId());
+ intent.putExtra(EXTRA_PREMIUM_CAPABILITY, capability);
+ return PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
+ }
+
+ /**
+ * Create the manage intent for when the user clicks the "Manage" button on the network boost
+ * notification. This will send {@link #ACTION_NOTIFICATION_MANAGE}.
+ *
+ * @param capability The premium capability that was requested.
+ * @return The manage intent.
+ */
+ @NonNull private PendingIntent getManageIntent(
+ @TelephonyManager.PremiumCapability int capability) {
+ Intent intent = new Intent(ACTION_NOTIFICATION_MANAGE);
+ intent.putExtra(EXTRA_PHONE_ID, mPhone.getPhoneId());
+ intent.putExtra(EXTRA_PREMIUM_CAPABILITY, capability);
+ return PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
+ }
+
+ private void cleanupBoosterNotification(@TelephonyManager.PremiumCapability int capability,
+ @TelephonyManager.PurchasePremiumCapabilityResult int result) {
+ mPhone.getContext().getSystemService(NotificationManager.class)
+ .cancel(NETWORK_BOOST_NOTIFICATION_TAG, capability);
+ mPhone.getContext().unregisterReceiver(mBroadcastReceivers.remove(capability));
+ Message onComplete = mPendingPurchaseCapabilities.remove(capability);
+ throttleCapability(capability, getThrottleDuration(result));
+ sendPurchaseResult(capability, result, onComplete);
+ }
+
+ private void onTimeout(@TelephonyManager.PremiumCapability int capability) {
+ log("onTimeout: " + TelephonyManager.convertPremiumCapabilityToString(capability));
+ cleanupBoosterNotification(capability,
+ TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT);
+ // TODO: Cancel SliceStoreActivity as well.
}
private void onUserCanceled(@TelephonyManager.PremiumCapability int capability) {
- // TODO(b/245882092): Process and return user canceled; throttle
+ log("onUserCanceled: " + TelephonyManager.convertPremiumCapabilityToString(capability));
+ if (hasMessages(EVENT_PURCHASE_TIMEOUT, capability)) {
+ log("onUserCanceled: Removing timeout for capability "
+ + TelephonyManager.convertPremiumCapabilityToString(capability));
+ removeMessages(EVENT_PURCHASE_TIMEOUT, capability);
+ }
+ cleanupBoosterNotification(capability,
+ TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED);
+ }
+
+ private void onUserDelayed(@TelephonyManager.PremiumCapability int capability) {
+ log("onUserDelayed: " + TelephonyManager.convertPremiumCapabilityToString(capability));
+ // TODO(b/245882092): implement
+ }
+
+ private void onUserManage(@TelephonyManager.PremiumCapability int capability) {
+ log("onUserManage: " + TelephonyManager.convertPremiumCapabilityToString(capability));
+ // TODO(b/245882092): implement
}
private void onUserConfirmed(@TelephonyManager.PremiumCapability int capability) {
@@ -361,7 +595,7 @@
return false;
}
- private @NetworkSliceInfo.SliceServiceType int getSliceServiceType(
+ @NetworkSliceInfo.SliceServiceType private int getSliceServiceType(
@TelephonyManager.PremiumCapability int capability) {
// TODO: Implement properly -- potentially need to add new slice service types?
return NetworkSliceInfo.SLICE_SERVICE_TYPE_NONE;
@@ -375,4 +609,8 @@
private void log(String s) {
Log.d(TAG + "-" + mPhone.getPhoneId(), s);
}
+
+ private void loge(String s) {
+ Log.e(TAG + "-" + mPhone.getPhoneId(), s);
+ }
}
diff --git a/src/com/android/phone/slicestore/SliceStoreActivity.java b/src/com/android/phone/slicestore/SliceStoreActivity.java
new file mode 100644
index 0000000..b93f757
--- /dev/null
+++ b/src/com/android/phone/slicestore/SliceStoreActivity.java
@@ -0,0 +1,34 @@
+/*
+ * 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.phone.slicestore;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Activity that launches when the user clicks on the network boost notification.
+ */
+public class SliceStoreActivity extends Activity {
+ private static final String TAG = "SliceStoreActivity";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.d(TAG, "onCreate");
+ }
+}