• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.phone.slice;
18 
19 import static android.telephony.TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS;
20 import static android.telephony.TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED;
21 import static android.telephony.TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED;
22 import static android.telephony.TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR;
23 import static android.telephony.TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED;
24 import static android.telephony.TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION;
25 
26 import android.annotation.IntDef;
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.app.PendingIntent;
30 import android.content.BroadcastReceiver;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.IntentFilter;
35 import android.content.SharedPreferences;
36 import android.net.ConnectivityManager;
37 import android.os.AsyncResult;
38 import android.os.Handler;
39 import android.os.HandlerThread;
40 import android.os.Looper;
41 import android.os.Message;
42 import android.os.PersistableBundle;
43 import android.provider.DeviceConfig;
44 import android.sysprop.TelephonyProperties;
45 import android.telephony.AnomalyReporter;
46 import android.telephony.CarrierConfigManager;
47 import android.telephony.SubscriptionManager;
48 import android.telephony.TelephonyManager;
49 import android.telephony.data.NetworkSliceInfo;
50 import android.telephony.data.NetworkSlicingConfig;
51 import android.telephony.data.RouteSelectionDescriptor;
52 import android.telephony.data.TrafficDescriptor;
53 import android.telephony.data.UrspRule;
54 import android.text.TextUtils;
55 import android.util.Log;
56 import android.webkit.URLUtil;
57 import android.webkit.WebView;
58 
59 import com.android.internal.annotations.VisibleForTesting;
60 import com.android.internal.telephony.Phone;
61 
62 import java.lang.annotation.Retention;
63 import java.lang.annotation.RetentionPolicy;
64 import java.net.MalformedURLException;
65 import java.net.URISyntaxException;
66 import java.net.URL;
67 import java.time.LocalDate;
68 import java.time.ZoneId;
69 import java.time.format.DateTimeParseException;
70 import java.util.Arrays;
71 import java.util.HashMap;
72 import java.util.HashSet;
73 import java.util.Map;
74 import java.util.Set;
75 import java.util.UUID;
76 import java.util.concurrent.Executor;
77 import java.util.concurrent.TimeUnit;
78 import java.util.function.Consumer;
79 
80 /**
81  * The SlicePurchaseController controls the purchase and availability of all cellular premium
82  * capabilities. Applications can check whether premium capabilities are available by calling
83  * {@link TelephonyManager#isPremiumCapabilityAvailableForPurchase(int)}. If this returns true,
84  * they can then call {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
85  * to purchase the premium capability. If all conditions are met, a notification will be displayed
86  * to the user prompting them to purchase the premium capability. If the user confirms on the
87  * notification, a {@link WebView} will open that allows the user to purchase the premium capability
88  * from the carrier. If the purchase is successful, the premium capability will be available for
89  * all applications to request through {@link ConnectivityManager#requestNetwork}.
90  */
91 public class SlicePurchaseController extends Handler {
92     @NonNull private static final String TAG = "SlicePurchaseController";
93 
94     /** Unknown failure code. */
95     public static final int FAILURE_CODE_UNKNOWN = 0;
96     /** Performance boost purchase failed because the carrier URL is unavailable. */
97     public static final int FAILURE_CODE_CARRIER_URL_UNAVAILABLE = 1;
98     /** Performance boost purchase failed because user authentication failed. */
99     public static final int FAILURE_CODE_AUTHENTICATION_FAILED = 2;
100     /** Performance boost purchase failed because the payment failed. */
101     public static final int FAILURE_CODE_PAYMENT_FAILED = 3;
102     /**
103      * Performance boost purchase failed because the content type was specified but
104      * user data does not exist.
105      */
106     public static final int FAILURE_CODE_NO_USER_DATA = 4;
107 
108     /**
109      * Failure codes that the carrier website can return when a premium capability purchase fails.
110      */
111     @Retention(RetentionPolicy.SOURCE)
112     @IntDef(prefix = { "FAILURE_CODE_" }, value = {
113             FAILURE_CODE_UNKNOWN,
114             FAILURE_CODE_CARRIER_URL_UNAVAILABLE,
115             FAILURE_CODE_AUTHENTICATION_FAILED,
116             FAILURE_CODE_PAYMENT_FAILED,
117             FAILURE_CODE_NO_USER_DATA})
118     public @interface FailureCode {}
119 
120     /** Value for an invalid premium capability. */
121     public static final int PREMIUM_CAPABILITY_INVALID = -1;
122 
123     /** Asset URL for the slice_purchase_test.html file. */
124     public static final String SLICE_PURCHASE_TEST_FILE =
125             "file:///android_asset/slice_purchase_test.html";
126 
127     /** Purchasing the premium capability is no longer throttled. */
128     private static final int EVENT_PURCHASE_UNTHROTTLED = 1;
129     /** Slicing config changed. */
130     private static final int EVENT_SLICING_CONFIG_CHANGED = 2;
131     /** Start slice purchase application. */
132     private static final int EVENT_START_SLICE_PURCHASE_APP = 3;
133     /**
134      * Premium capability was not purchased within the timeout specified by
135      * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG}.
136      */
137     private static final int EVENT_PURCHASE_TIMEOUT = 4;
138     /**
139      * Network did not set up the slicing configuration within the timeout specified by
140      * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG}.
141      */
142     private static final int EVENT_SETUP_TIMEOUT = 5;
143     /** Device config changed. */
144     private static final int EVENT_DEVICE_CONFIG_CHANGED = 6;
145 
146     /** UUID to report an anomaly when a premium capability is throttled twice in a row. */
147     private static final String UUID_CAPABILITY_THROTTLED_TWICE =
148             "15574927-e2e2-4593-99d4-2f340d22b383";
149     /** UUID to report an anomaly when receiving an invalid phone ID. */
150     private static final String UUID_INVALID_PHONE_ID = "ced79f1a-8ac0-4260-8cf3-08b54c0494f3";
151     /** UUID to report an anomaly when receiving an unknown action. */
152     private static final String UUID_UNKNOWN_ACTION = "0197efb0-dab1-4b0a-abaf-ac9336ec7923";
153     /** UUID to report an anomaly when receiving an unknown failure code with a non-empty reason. */
154     private static final String UUID_UNKNOWN_FAILURE_CODE = "76943b23-4415-400c-9855-b534fc4fc62c";
155     /**
156      * UUID to report an anomaly when the network fails to set up a slicing configuration after
157      * the user purchases a premium capability.
158      */
159     private static final String UUID_NETWORK_SETUP_FAILED = "12eeffbf-08f8-40ed-9a00-d344199552fc";
160 
161     /**
162      * Action to start the slice purchase application and display the
163      * performance boost notification.
164      */
165     public static final String ACTION_START_SLICE_PURCHASE_APP =
166             "com.android.phone.slice.action.START_SLICE_PURCHASE_APP";
167     /** Action indicating the premium capability purchase was not completed in time. */
168     public static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT =
169             "com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_TIMEOUT";
170     /** Action indicating the performance boost notification or WebView was canceled. */
171     private static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_CANCELED =
172             "com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_CANCELED";
173     /** Action indicating a carrier error prevented premium capability purchase. */
174     private static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_CARRIER_ERROR =
175             "com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_CARRIER_ERROR";
176     /**
177      * Action indicating a Telephony or slice purchase application error prevented premium
178      * capability purchase.
179      */
180     private static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_REQUEST_FAILED =
181             "com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_REQUEST_FAILED";
182     /** Action indicating the purchase request was not made on the default data subscription. */
183     private static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUBSCRIPTION =
184             "com.android.phone.slice.action."
185                     + "SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUBSCRIPTION";
186     /**
187      * Action indicating the performance boost notification was not shown because the user
188      * disabled notifications for the application or channel.
189      */
190     private static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED =
191             "com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED";
192     /** Action indicating the purchase request was successful. */
193     private static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS =
194             "com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_SUCCESS";
195     /**
196      * Action indicating the slice purchase application showed the performance boost notification.
197      */
198     private static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN =
199             "com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN";
200 
201     /** Extra for the phone index to send to the slice purchase application. */
202     public static final String EXTRA_PHONE_ID = "com.android.phone.slice.extra.PHONE_ID";
203     /** Extra for the subscription ID to send to the slice purchase application. */
204     public static final String EXTRA_SUB_ID = "com.android.phone.slice.extra.SUB_ID";
205     /**
206      * Extra for the requested premium capability to purchase from the slice purchase application.
207      */
208     public static final String EXTRA_PREMIUM_CAPABILITY =
209             "com.android.phone.slice.extra.PREMIUM_CAPABILITY";
210     /** Extra for the carrier URL to display to the user to allow premium capability purchase. */
211     public static final String EXTRA_PURCHASE_URL = "com.android.phone.slice.extra.PURCHASE_URL";
212     /** Extra for the duration of the purchased premium capability. */
213     public static final String EXTRA_PURCHASE_DURATION =
214             "com.android.phone.slice.extra.PURCHASE_DURATION";
215     /** Extra for the {@link FailureCode} why the premium capability purchase failed. */
216     public static final String EXTRA_FAILURE_CODE = "com.android.phone.slice.extra.FAILURE_CODE";
217     /** Extra for the human-readable reason why the premium capability purchase failed. */
218     public static final String EXTRA_FAILURE_REASON =
219             "com.android.phone.slice.extra.FAILURE_REASON";
220     /** Extra for the user's carrier. */
221     public static final String EXTRA_CARRIER = "com.android.phone.slice.extra.CARRIER";
222     /** Extra for the user data received from the entitlement service to send to the webapp. */
223     public static final String EXTRA_USER_DATA = "com.android.phone.slice.extra.USER_DATA";
224     /** Extra for the contents type received from the entitlement service to send to the webapp. */
225     public static final String EXTRA_CONTENTS_TYPE = "com.android.phone.slice.extra.CONTENTS_TYPE";
226     /**
227      * Extra for the canceled PendingIntent that the slice purchase application can send as a
228      * response if the performance boost notification or WebView was canceled by the user.
229      * Sends {@link #ACTION_SLICE_PURCHASE_APP_RESPONSE_CANCELED}.
230      */
231     public static final String EXTRA_INTENT_CANCELED =
232             "com.android.phone.slice.extra.INTENT_CANCELED";
233     /**
234      * Extra for the carrier error PendingIntent that the slice purchase application can send as a
235      * response if the premium capability purchase request failed due to a carrier error.
236      * Sends {@link #ACTION_SLICE_PURCHASE_APP_RESPONSE_CARRIER_ERROR}.
237      * Sender can modify the intent to specify the failure code and reason for failure with
238      * {@link #EXTRA_FAILURE_CODE} and {@link #EXTRA_FAILURE_REASON}.
239      */
240     public static final String EXTRA_INTENT_CARRIER_ERROR =
241             "com.android.phone.slice.extra.INTENT_CARRIER_ERROR";
242     /**
243      * Extra for the request failed PendingIntent that the slice purchase application can send as a
244      * response if the premium capability purchase request failed due to an error in Telephony or
245      * the slice purchase application.
246      * Sends {@link #ACTION_SLICE_PURCHASE_APP_RESPONSE_REQUEST_FAILED}.
247      */
248     public static final String EXTRA_INTENT_REQUEST_FAILED =
249             "com.android.phone.slice.extra.INTENT_REQUEST_FAILED";
250     /**
251      * Extra for the not-default data subscription ID PendingIntent that the slice purchase
252      * application can send as a response if the premium capability purchase request failed because
253      * it was not requested on the default data subscription.
254      * Sends {@link #ACTION_SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUBSCRIPTION}.
255      */
256     public static final String EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION =
257             "com.android.phone.slice.extra.INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION";
258     /**
259      * Extra for the notifications disabled PendingIntent that the slice purchase application can
260      * send as a response if the premium capability purchase request failed because the user
261      * disabled notifications for the application or channel.
262      * Sends {@link #ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED}.
263      */
264     public static final String EXTRA_INTENT_NOTIFICATIONS_DISABLED =
265             "com.android.phone.slice.extra.INTENT_NOTIFICATIONS_DISABLED";
266     /**
267      * Extra for the success PendingIntent that the slice purchase application can send as a
268      * response if the premium capability purchase request was successful.
269      * Sends {@link #ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS}.
270      * Sender can modify the intent to specify a purchase duration with
271      * {@link #EXTRA_PURCHASE_DURATION}.
272      */
273     public static final String EXTRA_INTENT_SUCCESS =
274             "com.android.phone.slice.extra.INTENT_SUCCESS";
275     /**
276      * Extra for the PendingIntent that the slice purchase application can send to indicate
277      * that it displayed the performance boost notification to the user.
278      * Sends {@link #ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN}.
279      */
280     public static final String EXTRA_INTENT_NOTIFICATION_SHOWN =
281             "com.android.phone.slice.extra.NOTIFICATION_SHOWN";
282 
283     /** Component name for the SlicePurchaseBroadcastReceiver. */
284     private static final ComponentName SLICE_PURCHASE_APP_COMPONENT_NAME =
285             ComponentName.unflattenFromString(
286                     "com.android.carrierdefaultapp/.SlicePurchaseBroadcastReceiver");
287 
288     /** Shared preference name for performance boost notification preferences. */
289     private static final String PERFORMANCE_BOOST_NOTIFICATION_PREFERENCES =
290             "performance_boost_notification_preferences";
291     /** Shared preference key for daily count of performance boost notifications. */
292     private static final String KEY_DAILY_NOTIFICATION_COUNT = "daily_notification_count";
293     /** Shared preference key for monthly count of performance boost notifications. */
294     private static final String KEY_MONTHLY_NOTIFICATION_COUNT = "monthly_notification_count";
295     /** DeviceConfig key for whether the slicing upsell feature is enabled. */
296     private static final String KEY_ENABLE_SLICING_UPSELL = "enable_slicing_upsell";
297     /**
298      * Shared preference key for the date the daily or monthly counts of performance boost
299      * notifications were last reset.
300      * A String with ISO-8601 format {@code YYYY-MM-DD}, from {@link LocalDate#toString}.
301      * For example, if the count was last updated on December 25, 2020, this would be `2020-12-25`.
302      */
303     private static final String KEY_NOTIFICATION_COUNT_LAST_RESET_DATE =
304             "notification_count_last_reset_date";
305 
306     /** Map of phone ID -> SlicePurchaseController instances. */
307     @NonNull private static final Map<Integer, SlicePurchaseController> sInstances =
308             new HashMap<>();
309 
310     /** The Phone instance used to create the SlicePurchaseController. */
311     @NonNull private final Phone mPhone;
312     /** The set of capabilities that are pending network setup. */
313     @NonNull private final Set<Integer> mPendingSetupCapabilities = new HashSet<>();
314     /** The set of throttled capabilities. */
315     @NonNull private final Set<Integer> mThrottledCapabilities = new HashSet<>();
316     /** A map of pending capabilities to the onComplete message for the purchase request. */
317     @NonNull private final Map<Integer, Message> mPendingPurchaseCapabilities = new HashMap<>();
318     /**
319      * A map of capabilities to the SlicePurchaseControllerBroadcastReceiver to handle
320      * slice purchase application responses.
321      */
322     @NonNull private final Map<Integer, SlicePurchaseControllerBroadcastReceiver>
323             mSlicePurchaseControllerBroadcastReceivers = new HashMap<>();
324     /** The current network slicing configuration. */
325     @Nullable private NetworkSlicingConfig mSlicingConfig;
326 
327     /** LocalDate to use when resetting notification counts. {@code null} except when testing. */
328     @Nullable private LocalDate mLocalDate;
329     /** The number of times the performance boost notification has been shown today. */
330     private int mDailyCount;
331     /** The number of times the performance boost notification has been shown this month. */
332     private int mMonthlyCount;
333     /** {@code true} if the slicing upsell feature is enabled and {@code false} otherwise. */
334     private boolean mIsSlicingUpsellEnabled;
335 
336     /**
337      * BroadcastReceiver to receive responses from the slice purchase application.
338      */
339     private class SlicePurchaseControllerBroadcastReceiver extends BroadcastReceiver {
340         @TelephonyManager.PremiumCapability private final int mCapability;
341 
342         /**
343          * Create a SlicePurchaseControllerBroadcastReceiver for the given capability
344          *
345          * @param capability The requested premium capability to listen to response for.
346          */
SlicePurchaseControllerBroadcastReceiver( @elephonyManager.PremiumCapability int capability)347         SlicePurchaseControllerBroadcastReceiver(
348                 @TelephonyManager.PremiumCapability int capability) {
349             mCapability = capability;
350         }
351 
352         /**
353          * Process responses from the slice purchase application.
354          *
355          * @param context The Context in which the receiver is running.
356          * @param intent The Intent being received.
357          */
358         @Override
onReceive(@onNull Context context, @NonNull Intent intent)359         public void onReceive(@NonNull Context context, @NonNull Intent intent) {
360             String action = intent.getAction();
361             logd("SlicePurchaseControllerBroadcastReceiver("
362                     + TelephonyManager.convertPremiumCapabilityToString(mCapability)
363                     + ") received action: " + action);
364             int phoneId = intent.getIntExtra(EXTRA_PHONE_ID,
365                     SubscriptionManager.INVALID_PHONE_INDEX);
366             int capability = intent.getIntExtra(EXTRA_PREMIUM_CAPABILITY,
367                     PREMIUM_CAPABILITY_INVALID);
368             if (SlicePurchaseController.getInstance(phoneId) == null) {
369                 reportAnomaly(UUID_INVALID_PHONE_ID, "SlicePurchaseControllerBroadcastReceiver( "
370                         + TelephonyManager.convertPremiumCapabilityToString(mCapability)
371                         + ") received invalid phoneId: " + phoneId);
372                 return;
373             } else if (capability != mCapability) {
374                 logd("SlicePurchaseControllerBroadcastReceiver("
375                         + TelephonyManager.convertPremiumCapabilityToString(mCapability)
376                         + ") ignoring intent for capability "
377                         + TelephonyManager.convertPremiumCapabilityToString(capability));
378                 return;
379             }
380             switch (action) {
381                 case ACTION_SLICE_PURCHASE_APP_RESPONSE_CANCELED: {
382                     logd("Slice purchase application canceled for capability: "
383                             + TelephonyManager.convertPremiumCapabilityToString(capability));
384                     SlicePurchaseController.getInstance(phoneId)
385                             .handlePurchaseResult(capability,
386                             TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED,
387                             true);
388                     break;
389                 }
390                 case ACTION_SLICE_PURCHASE_APP_RESPONSE_CARRIER_ERROR: {
391                     int failureCode = intent.getIntExtra(EXTRA_FAILURE_CODE, FAILURE_CODE_UNKNOWN);
392                     String failureReason = intent.getStringExtra(EXTRA_FAILURE_REASON);
393                     SlicePurchaseController.getInstance(phoneId).onCarrierError(
394                             capability, failureCode, failureReason);
395                     break;
396                 }
397                 case ACTION_SLICE_PURCHASE_APP_RESPONSE_REQUEST_FAILED: {
398                     logd("Purchase premium capability request failed for capability: "
399                             + TelephonyManager.convertPremiumCapabilityToString(capability));
400                     SlicePurchaseController.getInstance(phoneId)
401                             .handlePurchaseResult(capability,
402                             TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED,
403                             false);
404                     break;
405                 }
406                 case ACTION_SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUBSCRIPTION: {
407                     logd("Purchase premium capability request was not made on the default data "
408                             + "subscription for capability: "
409                             + TelephonyManager.convertPremiumCapabilityToString(capability));
410                     SlicePurchaseController.getInstance(phoneId)
411                             .handlePurchaseResult(capability,
412                             PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION,
413                             false);
414                     break;
415                 }
416                 case ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED: {
417                     logd("Slice purchase application unable to show notification for capability: "
418                             + TelephonyManager.convertPremiumCapabilityToString(capability)
419                             + " because the user has disabled notifications.");
420                     SlicePurchaseController.getInstance(phoneId)
421                             .handlePurchaseResult(capability,
422                             TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED,
423                             true);
424                     break;
425                 }
426                 case ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS: {
427                     long duration = intent.getLongExtra(EXTRA_PURCHASE_DURATION, 0);
428                     SlicePurchaseController.getInstance(phoneId).onCarrierSuccess(
429                             capability, duration);
430                     break;
431                 }
432                 case ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN: {
433                     SlicePurchaseController.getInstance(phoneId).onNotificationShown();
434                     break;
435                 }
436                 default:
437                     reportAnomaly(UUID_UNKNOWN_ACTION, "SlicePurchaseControllerBroadcastReceiver("
438                             + TelephonyManager.convertPremiumCapabilityToString(mCapability)
439                             + ") received unknown action: " + action);
440                     break;
441             }
442         }
443     }
444 
445     /**
446      * Get the static SlicePurchaseController instance for the given phone or create one if it
447      * doesn't exist.
448      *
449      * @param phone The Phone to get the SlicePurchaseController for.
450      * @return The static SlicePurchaseController instance.
451      */
getInstance(@onNull Phone phone)452     @NonNull public static synchronized SlicePurchaseController getInstance(@NonNull Phone phone) {
453         // TODO: Add listeners for multi sim setting changed (maybe carrier config changed too)
454         //  that dismiss notifications and update SlicePurchaseController instance
455         int phoneId = phone.getPhoneId();
456         if (sInstances.get(phoneId) == null) {
457             HandlerThread handlerThread = new HandlerThread("SlicePurchaseController");
458             handlerThread.start();
459             sInstances.put(phoneId, new SlicePurchaseController(phone, handlerThread.getLooper()));
460         }
461         return sInstances.get(phoneId);
462     }
463 
464     /**
465      * Get the static SlicePurchaseController instance for the given phone ID if it exists.
466      *
467      * @param phoneId The phone ID to get the SlicePurchaseController for.
468      * @return The static SlicePurchaseController instance or
469      *         {@code null} if it hasn't been created yet.
470      */
getInstance(int phoneId)471     @Nullable private static SlicePurchaseController getInstance(int phoneId) {
472         return sInstances.get(phoneId);
473     }
474 
475     /**
476      * Create a SlicePurchaseController for the given phone on the given looper.
477      *
478      * @param phone The Phone to create the SlicePurchaseController for.
479      * @param looper The Looper to run the SlicePurchaseController on.
480      */
481     @VisibleForTesting
SlicePurchaseController(@onNull Phone phone, @NonNull Looper looper)482     public SlicePurchaseController(@NonNull Phone phone, @NonNull Looper looper) {
483         super(looper);
484         mPhone = phone;
485         // TODO: Create a cached value for slicing config in DataIndication and initialize here
486         mPhone.mCi.registerForSlicingConfigChanged(this, EVENT_SLICING_CONFIG_CHANGED, null);
487         mIsSlicingUpsellEnabled = DeviceConfig.getBoolean(
488                 DeviceConfig.NAMESPACE_TELEPHONY, KEY_ENABLE_SLICING_UPSELL, false);
489         DeviceConfig.addOnPropertiesChangedListener(
490                 DeviceConfig.NAMESPACE_TELEPHONY, this::post,
491                 properties -> {
492                     if (TextUtils.equals(DeviceConfig.NAMESPACE_TELEPHONY,
493                             properties.getNamespace())) {
494                         sendEmptyMessage(EVENT_DEVICE_CONFIG_CHANGED);
495                     }
496                 });
497         updateNotificationCounts();
498     }
499 
500     /**
501      * Set the LocalDate to use for resetting daily and monthly notification counts.
502      *
503      * @param localDate The LocalDate instance to use.
504      */
505     @VisibleForTesting
setLocalDate(@onNull LocalDate localDate)506     public void setLocalDate(@NonNull LocalDate localDate) {
507         mLocalDate = localDate;
508     }
509 
510     /**
511      * Set the NetworkSlicingConfig to use for determining whether the premium capability was
512      * successfully set up on the carrier network.
513      *
514      * @param slicingConfig The LocalDate instance to use.
515      */
516     @VisibleForTesting
setSlicingConfig(@onNull NetworkSlicingConfig slicingConfig)517     public void setSlicingConfig(@NonNull NetworkSlicingConfig slicingConfig) {
518         mSlicingConfig = slicingConfig;
519     }
520 
521     @Override
handleMessage(@onNull Message msg)522     public void handleMessage(@NonNull Message msg) {
523         switch (msg.what) {
524             case EVENT_PURCHASE_UNTHROTTLED: {
525                 int capability = (int) msg.obj;
526                 logd("EVENT_PURCHASE_UNTHROTTLED: for capability "
527                         + TelephonyManager.convertPremiumCapabilityToString(capability));
528                 mThrottledCapabilities.remove(capability);
529                 break;
530             }
531             case EVENT_SLICING_CONFIG_CHANGED: {
532                 AsyncResult ar = (AsyncResult) msg.obj;
533                 NetworkSlicingConfig config = (NetworkSlicingConfig) ar.result;
534                 logd("EVENT_SLICING_CONFIG_CHANGED: previous= " + mSlicingConfig);
535                 logd("EVENT_SLICING_CONFIG_CHANGED: current= " + config);
536                 mSlicingConfig = config;
537                 onSlicingConfigChanged();
538                 break;
539             }
540             case EVENT_START_SLICE_PURCHASE_APP: {
541                 int capability = (int) msg.obj;
542                 logd("EVENT_START_SLICE_PURCHASE_APP: "
543                         + TelephonyManager.convertPremiumCapabilityToString(capability));
544                 onStartSlicePurchaseApplication(capability);
545                 break;
546             }
547             case EVENT_PURCHASE_TIMEOUT: {
548                 int capability = (int) msg.obj;
549                 logd("EVENT_PURCHASE_TIMEOUT: for capability "
550                         + TelephonyManager.convertPremiumCapabilityToString(capability));
551                 onTimeout(capability);
552                 break;
553             }
554             case EVENT_SETUP_TIMEOUT:
555                 int capability = (int) msg.obj;
556                 logd("EVENT_SETUP_TIMEOUT: for capability "
557                         + TelephonyManager.convertPremiumCapabilityToString(capability));
558                 onSetupTimeout(capability);
559                 break;
560             case EVENT_DEVICE_CONFIG_CHANGED:
561                 boolean isSlicingUpsellEnabled = DeviceConfig.getBoolean(
562                         DeviceConfig.NAMESPACE_TELEPHONY, KEY_ENABLE_SLICING_UPSELL, false);
563                 if (isSlicingUpsellEnabled != mIsSlicingUpsellEnabled) {
564                     logd("EVENT_DEVICE_CONFIG_CHANGED: from " + mIsSlicingUpsellEnabled + " to "
565                             + isSlicingUpsellEnabled);
566                     mIsSlicingUpsellEnabled = isSlicingUpsellEnabled;
567                 }
568                 break;
569             default:
570                 loge("Unknown event: " + msg.obj);
571         }
572     }
573 
574     /**
575      * Check whether the given premium capability is available for purchase from the carrier.
576      *
577      * @param capability The premium capability to check.
578      * @return Whether the given premium capability is available to purchase.
579      */
isPremiumCapabilityAvailableForPurchase( @elephonyManager.PremiumCapability int capability)580     public boolean isPremiumCapabilityAvailableForPurchase(
581             @TelephonyManager.PremiumCapability int capability) {
582         if (!arePremiumCapabilitiesSupportedByDevice()) {
583             logd("Premium capabilities unsupported by the device.");
584             return false;
585         }
586         if (!isPremiumCapabilitySupportedByCarrier(capability)) {
587             logd("Premium capability "
588                     + TelephonyManager.convertPremiumCapabilityToString(capability)
589                     + " unsupported by the carrier.");
590             return false;
591         }
592         if (!isDefaultDataSub()) {
593             logd("Premium capability "
594                     + TelephonyManager.convertPremiumCapabilityToString(capability)
595                     + " unavailable on the non-default data subscription.");
596             return false;
597         }
598         logd("Premium capability "
599                 + TelephonyManager.convertPremiumCapabilityToString(capability)
600                 + " is available for purchase.");
601         return true;
602     }
603 
604     /**
605      * Purchase the given premium capability from the carrier.
606      *
607      * @param capability The premium capability to purchase.
608      * @param onComplete The callback message to send when the purchase request is complete.
609      */
purchasePremiumCapability( @elephonyManager.PremiumCapability int capability, @NonNull Message onComplete)610     public synchronized void purchasePremiumCapability(
611             @TelephonyManager.PremiumCapability int capability, @NonNull Message onComplete) {
612         logd("purchasePremiumCapability: "
613                 + TelephonyManager.convertPremiumCapabilityToString(capability));
614         // Check whether the premium capability can be purchased.
615         if (!arePremiumCapabilitiesSupportedByDevice()) {
616             sendPurchaseResult(capability,
617                     TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED,
618                     onComplete);
619             return;
620         }
621         if (!isPremiumCapabilitySupportedByCarrier(capability)) {
622             sendPurchaseResult(capability,
623                     PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED,
624                     onComplete);
625             return;
626         }
627         if (!isDefaultDataSub()) {
628             sendPurchaseResult(capability,
629                     PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION,
630                     onComplete);
631             return;
632         }
633         if (isSlicingConfigActive(capability)) {
634             sendPurchaseResult(capability,
635                     PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED,
636                     onComplete);
637             return;
638         }
639         if (mPendingSetupCapabilities.contains(capability)) {
640             sendPurchaseResult(capability,
641                     TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP,
642                     onComplete);
643             return;
644         }
645         if (mThrottledCapabilities.contains(capability)) {
646             sendPurchaseResult(capability,
647                     TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED,
648                     onComplete);
649             return;
650         }
651         if (!isNetworkAvailable()) {
652             sendPurchaseResult(capability,
653                     TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
654                     onComplete);
655             return;
656         }
657 
658         if (mPendingPurchaseCapabilities.containsKey(capability)) {
659             sendPurchaseResult(capability,
660                     PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS,
661                     onComplete);
662             return;
663         }
664 
665         // All state checks passed. Mark purchase pending and start the slice purchase application.
666         // Process through the handler since this method is synchronized.
667         mPendingPurchaseCapabilities.put(capability, onComplete);
668         sendMessage(obtainMessage(EVENT_START_SLICE_PURCHASE_APP, capability));
669     }
670 
sendPurchaseResult(@elephonyManager.PremiumCapability int capability, @TelephonyManager.PurchasePremiumCapabilityResult int result, @NonNull Message onComplete)671     private void sendPurchaseResult(@TelephonyManager.PremiumCapability int capability,
672             @TelephonyManager.PurchasePremiumCapabilityResult int result,
673             @NonNull Message onComplete) {
674         // Send the onComplete message with the purchase result.
675         logd("Purchase result for capability "
676                 + TelephonyManager.convertPremiumCapabilityToString(capability)
677                 + ": " + TelephonyManager.convertPurchaseResultToString(result));
678         AsyncResult.forMessage(onComplete, result, null);
679         onComplete.sendToTarget();
680     }
681 
handlePurchaseResult( @elephonyManager.PremiumCapability int capability, @TelephonyManager.PurchasePremiumCapabilityResult int result, boolean throttle)682     private void handlePurchaseResult(
683             @TelephonyManager.PremiumCapability int capability,
684             @TelephonyManager.PurchasePremiumCapabilityResult int result, boolean throttle) {
685         SlicePurchaseControllerBroadcastReceiver receiver =
686                 mSlicePurchaseControllerBroadcastReceivers.remove(capability);
687         if (receiver != null) {
688             mPhone.getContext().unregisterReceiver(receiver);
689         }
690         removeMessages(EVENT_PURCHASE_TIMEOUT, capability);
691         if (throttle) {
692             throttleCapability(capability, getThrottleDuration(result));
693         }
694         sendPurchaseResult(capability, result, mPendingPurchaseCapabilities.remove(capability));
695     }
696 
throttleCapability(@elephonyManager.PremiumCapability int capability, long throttleDuration)697     private void throttleCapability(@TelephonyManager.PremiumCapability int capability,
698             long throttleDuration) {
699         // Throttle subsequent requests if necessary.
700         if (!mThrottledCapabilities.contains(capability)) {
701             if (throttleDuration > 0) {
702                 logd("Throttle purchase requests for capability "
703                         + TelephonyManager.convertPremiumCapabilityToString(capability) + " for "
704                         + TimeUnit.MILLISECONDS.toMinutes(throttleDuration) + " minutes.");
705                 mThrottledCapabilities.add(capability);
706                 sendMessageDelayed(obtainMessage(EVENT_PURCHASE_UNTHROTTLED, capability),
707                         throttleDuration);
708             }
709         } else {
710             reportAnomaly(UUID_CAPABILITY_THROTTLED_TWICE,
711                     TelephonyManager.convertPremiumCapabilityToString(capability)
712                             + " is already throttled.");
713         }
714     }
715 
onSlicingConfigChanged()716     private void onSlicingConfigChanged() {
717         for (int capability : new int[] {TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY}) {
718             if (isSlicingConfigActive(capability) && hasMessages(EVENT_SETUP_TIMEOUT, capability)) {
719                 logd("Successfully set up slicing configuration for "
720                         + TelephonyManager.convertPremiumCapabilityToString(capability));
721                 mPendingSetupCapabilities.remove(capability);
722                 removeMessages(EVENT_SETUP_TIMEOUT, capability);
723             }
724         }
725     }
726 
727     /**
728      * @return A new PremiumNetworkEntitlementApi object.
729      */
730     @VisibleForTesting
getPremiumNetworkEntitlementApi()731     public PremiumNetworkEntitlementApi getPremiumNetworkEntitlementApi() {
732         return new PremiumNetworkEntitlementApi(mPhone, getCarrierConfigs());
733     }
734 
onStartSlicePurchaseApplication( @elephonyManager.PremiumCapability int capability)735     private void onStartSlicePurchaseApplication(
736             @TelephonyManager.PremiumCapability int capability) {
737         updateNotificationCounts();
738         if (mMonthlyCount >= getCarrierConfigs().getInt(
739                 CarrierConfigManager.KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT)
740                 || mDailyCount >= getCarrierConfigs().getInt(
741                 CarrierConfigManager.KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT)) {
742             logd("Reached maximum number of performance boost notifications.");
743             handlePurchaseResult(capability,
744                     TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, false);
745             return;
746         }
747 
748         final PremiumNetworkEntitlementApi premiumNetworkEntitlementApi =
749                 getPremiumNetworkEntitlementApi();
750         PremiumNetworkEntitlementResponse premiumNetworkEntitlementResponse =
751                 premiumNetworkEntitlementApi.checkEntitlementStatus(capability);
752 
753         // invalid response for entitlement check
754         if (premiumNetworkEntitlementResponse == null
755                 || premiumNetworkEntitlementResponse.isInvalidResponse()) {
756             loge("Invalid response for entitlement check: " + premiumNetworkEntitlementResponse);
757             handlePurchaseResult(capability,
758                     PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR, true);
759             return;
760         }
761 
762         if (!premiumNetworkEntitlementResponse.isPremiumNetworkCapabilityAllowed()) {
763             loge("Entitlement Check: Not allowed.");
764             handlePurchaseResult(capability,
765                     PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED, true);
766             return;
767         }
768 
769         if (premiumNetworkEntitlementResponse.isAlreadyPurchased()) {
770             logd("Entitlement Check: Already purchased/provisioned.");
771             handlePurchaseResult(capability,
772                     PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED, true);
773             return;
774         }
775 
776         if (premiumNetworkEntitlementResponse.isProvisioningInProgress()) {
777             logd("Entitlement Check: In progress.");
778             handlePurchaseResult(capability,
779                     PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS, true);
780             return;
781         }
782 
783         String purchaseUrl = getPurchaseUrl(premiumNetworkEntitlementResponse);
784         String carrier = getSimOperator();
785         if (TextUtils.isEmpty(purchaseUrl) || TextUtils.isEmpty(carrier)) {
786             handlePurchaseResult(capability,
787                     PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED, false);
788             return;
789         }
790 
791         // Start timeout for purchase completion.
792         long timeout = getCarrierConfigs().getLong(CarrierConfigManager
793                 .KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG);
794         logd("Start purchase timeout for "
795                 + TelephonyManager.convertPremiumCapabilityToString(capability) + " for "
796                 + TimeUnit.MILLISECONDS.toMinutes(timeout) + " minutes.");
797         sendMessageDelayed(obtainMessage(EVENT_PURCHASE_TIMEOUT, capability), timeout);
798 
799         // Broadcast start intent to start the slice purchase application
800         Intent intent = new Intent(ACTION_START_SLICE_PURCHASE_APP);
801         intent.setComponent(SLICE_PURCHASE_APP_COMPONENT_NAME);
802         intent.putExtra(EXTRA_PHONE_ID, mPhone.getPhoneId());
803         intent.putExtra(EXTRA_SUB_ID, mPhone.getSubId());
804         intent.putExtra(EXTRA_PREMIUM_CAPABILITY, capability);
805         intent.putExtra(EXTRA_PURCHASE_URL, purchaseUrl);
806         intent.putExtra(EXTRA_CARRIER, carrier);
807         intent.putExtra(EXTRA_USER_DATA, premiumNetworkEntitlementResponse.mServiceFlowUserData);
808         intent.putExtra(EXTRA_CONTENTS_TYPE,
809                 premiumNetworkEntitlementResponse.mServiceFlowContentsType);
810         intent.putExtra(EXTRA_INTENT_CANCELED, createPendingIntent(
811                 ACTION_SLICE_PURCHASE_APP_RESPONSE_CANCELED, capability, false));
812         intent.putExtra(EXTRA_INTENT_CARRIER_ERROR, createPendingIntent(
813                 ACTION_SLICE_PURCHASE_APP_RESPONSE_CARRIER_ERROR, capability, true));
814         intent.putExtra(EXTRA_INTENT_REQUEST_FAILED, createPendingIntent(
815                 ACTION_SLICE_PURCHASE_APP_RESPONSE_REQUEST_FAILED, capability, false));
816         intent.putExtra(EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION, createPendingIntent(
817                 ACTION_SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUBSCRIPTION, capability,
818                 false));
819         intent.putExtra(EXTRA_INTENT_NOTIFICATIONS_DISABLED, createPendingIntent(
820                 ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED, capability, false));
821         intent.putExtra(EXTRA_INTENT_SUCCESS, createPendingIntent(
822                 ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS, capability, true));
823         intent.putExtra(EXTRA_INTENT_NOTIFICATION_SHOWN, createPendingIntent(
824                 ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN, capability, false));
825         logd("Broadcasting start intent to SlicePurchaseBroadcastReceiver.");
826         mPhone.getContext().sendBroadcast(intent);
827 
828         // Listen for responses from the slice purchase application
829         mSlicePurchaseControllerBroadcastReceivers.put(capability,
830                 new SlicePurchaseControllerBroadcastReceiver(capability));
831         IntentFilter filter = new IntentFilter();
832         filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_CANCELED);
833         filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_CARRIER_ERROR);
834         filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_REQUEST_FAILED);
835         filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUBSCRIPTION);
836         filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED);
837         filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS);
838         filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN);
839         mPhone.getContext().registerReceiver(
840                 mSlicePurchaseControllerBroadcastReceivers.get(capability), filter,
841                 Context.RECEIVER_NOT_EXPORTED);
842     }
843 
844     /**
845      * Get a valid purchase URL from either entitlement response or carrier configs, if one exists.
846      *
847      * @param entitlementResponse The entitlement response to get the purchase URL from.
848      * @return A valid purchase URL or an empty string if one doesn't exist.
849      */
850     @VisibleForTesting
getPurchaseUrl( @onNull PremiumNetworkEntitlementResponse entitlementResponse)851     @NonNull public String getPurchaseUrl(
852             @NonNull PremiumNetworkEntitlementResponse entitlementResponse) {
853         String purchaseUrl = entitlementResponse.mServiceFlowURL;
854         if (!isUrlValid(purchaseUrl)) {
855             purchaseUrl = getCarrierConfigs().getString(
856                     CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
857             if (!isUrlValid(purchaseUrl)) {
858                 purchaseUrl = "";
859             }
860         }
861         return purchaseUrl;
862     }
863 
864     /**
865      * Get the SIM operator. This is the carrier name from the SIM rather than from the network,
866      * which will be the same regardless of whether the user is roaming or not.
867      *
868      * @return The operator name from the SIM.
869      */
870     @VisibleForTesting
getSimOperator()871     @Nullable public String getSimOperator() {
872         if (mPhone.getPhoneId() < TelephonyProperties.icc_operator_alpha().size()) {
873             return TelephonyProperties.icc_operator_alpha().get(mPhone.getPhoneId());
874         }
875         return null;
876     }
877 
878     /**
879      * Create the PendingIntent to allow the slice purchase application to send back responses.
880      *
881      * @param action The action that will be sent for this PendingIntent
882      * @param capability The premium capability that was requested.
883      * @param mutable {@code true} if the PendingIntent should be mutable and
884      *                {@code false} if it should be immutable.
885      * @return The PendingIntent for the given action and capability.
886      */
887     @VisibleForTesting
createPendingIntent(@onNull String action, @TelephonyManager.PremiumCapability int capability, boolean mutable)888     @NonNull public PendingIntent createPendingIntent(@NonNull String action,
889             @TelephonyManager.PremiumCapability int capability, boolean mutable) {
890         Intent intent = new Intent(action);
891         intent.putExtra(EXTRA_PHONE_ID, mPhone.getPhoneId());
892         intent.putExtra(EXTRA_PREMIUM_CAPABILITY, capability);
893         intent.setPackage(mPhone.getContext().getPackageName());
894         return PendingIntent.getBroadcast(mPhone.getContext(), capability, intent,
895                 PendingIntent.FLAG_CANCEL_CURRENT
896                         | (mutable ? PendingIntent.FLAG_MUTABLE : PendingIntent.FLAG_IMMUTABLE));
897     }
898 
onTimeout(@elephonyManager.PremiumCapability int capability)899     private void onTimeout(@TelephonyManager.PremiumCapability int capability) {
900         logd("onTimeout: " + TelephonyManager.convertPremiumCapabilityToString(capability));
901         // Broadcast timeout intent to clean up the slice purchase notification and activity
902         Intent intent = new Intent(ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT);
903         intent.setComponent(SLICE_PURCHASE_APP_COMPONENT_NAME);
904         intent.putExtra(EXTRA_PHONE_ID, mPhone.getPhoneId());
905         intent.putExtra(EXTRA_PREMIUM_CAPABILITY, capability);
906         logd("Broadcasting timeout intent to SlicePurchaseBroadcastReceiver.");
907         mPhone.getContext().sendBroadcast(intent);
908 
909         handlePurchaseResult(
910                 capability, TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT, true);
911     }
912 
onCarrierError(@elephonyManager.PremiumCapability int capability, @FailureCode int failureCode, @Nullable String failureReason)913     private void onCarrierError(@TelephonyManager.PremiumCapability int capability,
914             @FailureCode int failureCode, @Nullable String failureReason) {
915         logd("Carrier error for capability: "
916                 + TelephonyManager.convertPremiumCapabilityToString(capability) + " with code: "
917                 + convertFailureCodeToString(failureCode) + " and reason: " + failureReason);
918         if (failureCode == FAILURE_CODE_UNKNOWN && !TextUtils.isEmpty(failureReason)) {
919             reportAnomaly(UUID_UNKNOWN_FAILURE_CODE,
920                     "Failure code needs to be added for: " + failureReason);
921         }
922         handlePurchaseResult(capability,
923                 TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR, true);
924     }
925 
onCarrierSuccess(@elephonyManager.PremiumCapability int capability, long duration)926     private void onCarrierSuccess(@TelephonyManager.PremiumCapability int capability,
927             long duration) {
928         logd("Successfully purchased premium capability "
929                 + TelephonyManager.convertPremiumCapabilityToString(capability) + (duration > 0
930                 ? " for " + TimeUnit.MILLISECONDS.toMinutes(duration) + " minutes." : "."));
931         mPendingSetupCapabilities.add(capability);
932         long setupDuration = getCarrierConfigs().getLong(
933                 CarrierConfigManager.KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG);
934         logd("Waiting " + TimeUnit.MILLISECONDS.toMinutes(setupDuration) + " minutes for the "
935                 + "network to set up the slicing configuration.");
936         sendMessageDelayed(obtainMessage(EVENT_SETUP_TIMEOUT, capability), setupDuration);
937         handlePurchaseResult(
938                 capability, TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS, false);
939     }
940 
onSetupTimeout(@elephonyManager.PremiumCapability int capability)941     private void onSetupTimeout(@TelephonyManager.PremiumCapability int capability) {
942         logd("onSetupTimeout: " + TelephonyManager.convertPremiumCapabilityToString(capability));
943         mPendingSetupCapabilities.remove(capability);
944         if (!isSlicingConfigActive(capability)) {
945             reportAnomaly(UUID_NETWORK_SETUP_FAILED,
946                     "Failed to set up slicing configuration for capability "
947                             + TelephonyManager.convertPremiumCapabilityToString(capability)
948                             + " within the time specified.");
949         }
950     }
951 
onNotificationShown()952     private void onNotificationShown() {
953         SharedPreferences sp = mPhone.getContext().getSharedPreferences(
954                 PERFORMANCE_BOOST_NOTIFICATION_PREFERENCES, 0);
955         mDailyCount = sp.getInt((KEY_DAILY_NOTIFICATION_COUNT + mPhone.getPhoneId()), 0) + 1;
956         mMonthlyCount = sp.getInt((KEY_MONTHLY_NOTIFICATION_COUNT + mPhone.getPhoneId()), 0) + 1;
957         logd("Performance boost notification was shown " + mDailyCount + " times today and "
958                 + mMonthlyCount + " times this month.");
959 
960         SharedPreferences.Editor editor = sp.edit();
961         editor.putInt((KEY_DAILY_NOTIFICATION_COUNT + mPhone.getPhoneId()), mDailyCount);
962         editor.putInt((KEY_MONTHLY_NOTIFICATION_COUNT + mPhone.getPhoneId()), mMonthlyCount);
963         editor.apply();
964 
965         // Don't call updateNotificationCounts here because it will be called whenever a new
966         // purchase request comes in or when SlicePurchaseController is initialized.
967     }
968 
969     /**
970      * Update the current daily and monthly performance boost notification counts.
971      * If it has been at least a day since the last daily reset or at least a month since the last
972      * monthly reset, reset the current daily or monthly notification counts.
973      */
974     @VisibleForTesting
updateNotificationCounts()975     public void updateNotificationCounts() {
976         SharedPreferences sp = mPhone.getContext().getSharedPreferences(
977                 PERFORMANCE_BOOST_NOTIFICATION_PREFERENCES, 0);
978         mDailyCount = sp.getInt((KEY_DAILY_NOTIFICATION_COUNT + mPhone.getPhoneId()), 0);
979         mMonthlyCount = sp.getInt((KEY_MONTHLY_NOTIFICATION_COUNT + mPhone.getPhoneId()), 0);
980 
981         if (mLocalDate == null) {
982             // Standardize to UTC to prevent default time zone dependency
983             mLocalDate = LocalDate.now(ZoneId.of("UTC"));
984         }
985         LocalDate lastLocalDate = LocalDate.of(1, 1, 1);
986         String lastLocalDateString = sp.getString(
987                 (KEY_NOTIFICATION_COUNT_LAST_RESET_DATE + mPhone.getPhoneId()), "");
988         if (!TextUtils.isEmpty(lastLocalDateString)) {
989             try {
990                 lastLocalDate = LocalDate.parse(lastLocalDateString);
991             } catch (DateTimeParseException e) {
992                 loge("Error parsing LocalDate from SharedPreferences: " + e);
993             }
994         }
995         logd("updateNotificationCounts: mDailyCount=" + mDailyCount + ", mMonthlyCount="
996                 + mMonthlyCount + ", mLocalDate=" + mLocalDate + ", lastLocalDate="
997                 + lastLocalDate);
998 
999         boolean resetMonthly = lastLocalDate.getYear() != mLocalDate.getYear()
1000                 || lastLocalDate.getMonthValue() != mLocalDate.getMonthValue();
1001         boolean resetDaily = resetMonthly
1002                 || lastLocalDate.getDayOfMonth() != mLocalDate.getDayOfMonth();
1003         if (resetDaily) {
1004             logd("Resetting daily" + (resetMonthly ? " and monthly" : "") + " notification count.");
1005             SharedPreferences.Editor editor = sp.edit();
1006             if (resetMonthly) {
1007                 mMonthlyCount = 0;
1008                 editor.putInt((KEY_MONTHLY_NOTIFICATION_COUNT + mPhone.getPhoneId()),
1009                         mMonthlyCount);
1010             }
1011             mDailyCount = 0;
1012             editor.putInt((KEY_DAILY_NOTIFICATION_COUNT + mPhone.getPhoneId()), mDailyCount);
1013             editor.putString((KEY_NOTIFICATION_COUNT_LAST_RESET_DATE + mPhone.getPhoneId()),
1014                     mLocalDate.toString());
1015             editor.apply();
1016         }
1017     }
1018 
getCarrierConfigs()1019     @Nullable private PersistableBundle getCarrierConfigs() {
1020         return mPhone.getContext().getSystemService(CarrierConfigManager.class)
1021                 .getConfigForSubId(mPhone.getSubId());
1022     }
1023 
getThrottleDuration(@elephonyManager.PurchasePremiumCapabilityResult int result)1024     private long getThrottleDuration(@TelephonyManager.PurchasePremiumCapabilityResult int result) {
1025         if (result == TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
1026                 || result == TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
1027                 || result == TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED) {
1028             return getCarrierConfigs().getLong(CarrierConfigManager
1029                     .KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG);
1030         }
1031         if (result == TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED
1032                 || result == TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR) {
1033             return getCarrierConfigs().getLong(CarrierConfigManager
1034                     .KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG);
1035         }
1036         return 0;
1037     }
1038 
isPremiumCapabilitySupportedByCarrier( @elephonyManager.PremiumCapability int capability)1039     private boolean isPremiumCapabilitySupportedByCarrier(
1040             @TelephonyManager.PremiumCapability int capability) {
1041         int[] supportedCapabilities = getCarrierConfigs().getIntArray(
1042                 CarrierConfigManager.KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY);
1043         if (supportedCapabilities == null) {
1044             logd("No premium capabilities are supported by the carrier.");
1045             return false;
1046         }
1047         return Arrays.stream(supportedCapabilities)
1048                 .anyMatch(supportedCapability -> supportedCapability == capability);
1049     }
1050 
isUrlValid(@ullable String url)1051     private boolean isUrlValid(@Nullable String url) {
1052         if (!URLUtil.isValidUrl(url)) {
1053             loge("Invalid URL: " + url);
1054             return false;
1055         }
1056         if (URLUtil.isAssetUrl(url) && !url.equals(SLICE_PURCHASE_TEST_FILE)) {
1057             loge("Invalid asset: " + url);
1058             return false;
1059         }
1060         try {
1061             new URL(url).toURI();
1062         } catch (MalformedURLException | URISyntaxException e) {
1063             loge("Invalid URI: " + url);
1064             return false;
1065         }
1066         logd("Valid URL: " + url);
1067         return true;
1068     }
1069 
arePremiumCapabilitiesSupportedByDevice()1070     private boolean arePremiumCapabilitiesSupportedByDevice() {
1071         if ((mPhone.getCachedAllowedNetworkTypesBitmask()
1072                 & TelephonyManager.NETWORK_TYPE_BITMASK_NR) == 0) {
1073             logd("Premium capabilities unsupported because NR is not allowed on the device.");
1074             return false;
1075         }
1076         if (!mIsSlicingUpsellEnabled) {
1077             logd("Premium capabilities unsupported because "
1078                     + "slicing upsell is disabled on the device.");
1079         }
1080         return mIsSlicingUpsellEnabled;
1081     }
1082 
isDefaultDataSub()1083     private boolean isDefaultDataSub() {
1084         return mPhone.getSubId() == SubscriptionManager.getDefaultDataSubscriptionId();
1085     }
1086 
1087     /**
1088      * Check whether the current network slicing configuration indicates that the given premium
1089      * capability is active and set up on the carrier network.
1090      * @param capability The premium capability to check for.
1091      * @return {@code true} if the slicing config indicates the capability is active and
1092      * {@code false} otherwise.
1093      */
1094     @VisibleForTesting
isSlicingConfigActive(@elephonyManager.PremiumCapability int capability)1095     public boolean isSlicingConfigActive(@TelephonyManager.PremiumCapability int capability) {
1096         if (mSlicingConfig == null) {
1097             return false;
1098         }
1099         for (UrspRule urspRule : mSlicingConfig.getUrspRules()) {
1100             for (TrafficDescriptor trafficDescriptor : urspRule.getTrafficDescriptors()) {
1101                 TrafficDescriptor.OsAppId osAppId =
1102                         new TrafficDescriptor.OsAppId(trafficDescriptor.getOsAppId());
1103                 if (osAppId.getAppId().equals(getAppId(capability))) {
1104                     for (RouteSelectionDescriptor rsd : urspRule.getRouteSelectionDescriptor()) {
1105                         for (NetworkSliceInfo sliceInfo : rsd.getSliceInfo()) {
1106                             if (sliceInfo.getStatus() == NetworkSliceInfo.SLICE_STATUS_ALLOWED
1107                                     && getSliceServiceTypes(capability).contains(
1108                                             sliceInfo.getSliceServiceType())) {
1109                                 return true;
1110                             }
1111                         }
1112                     }
1113                 }
1114             }
1115         }
1116         return false;
1117     }
1118 
1119     /**
1120      * Get the application ID associated with the given premium capability.
1121      * The app ID is a field in {@link TrafficDescriptor} that helps match URSP rules to determine
1122      * whether the premium capability was successfully set up on the carrier network.
1123      * @param capability The premium capability to get the app ID for.
1124      * @return The application ID associated with the premium capability.
1125      */
1126     @VisibleForTesting
getAppId(@elephonyManager.PremiumCapability int capability)1127     @NonNull public static String getAppId(@TelephonyManager.PremiumCapability int capability) {
1128         if (capability == TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY) {
1129             return "PRIORITIZE_LATENCY";
1130         }
1131         return "";
1132     }
1133 
1134     /**
1135      * Get the slice service types associated with the given premium capability.
1136      * The slice service type is a field in {@link NetworkSliceInfo} that helps to match determine
1137      * whether the premium capability was successfully set up on the carrier network.
1138      * @param capability The premium capability to get the associated slice service types for.
1139      * @return A set of slice service types associated with the premium capability.
1140      */
1141     @VisibleForTesting
getSliceServiceTypes( @elephonyManager.PremiumCapability int capability)1142     @NonNull @NetworkSliceInfo.SliceServiceType public static Set<Integer> getSliceServiceTypes(
1143             @TelephonyManager.PremiumCapability int capability) {
1144         Set<Integer> sliceServiceTypes = new HashSet<>();
1145         if (capability == TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY) {
1146             sliceServiceTypes.add(NetworkSliceInfo.SLICE_SERVICE_TYPE_EMBB);
1147             sliceServiceTypes.add(NetworkSliceInfo.SLICE_SERVICE_TYPE_URLLC);
1148         } else {
1149             sliceServiceTypes.add(NetworkSliceInfo.SLICE_SERVICE_TYPE_NONE);
1150         }
1151         return sliceServiceTypes;
1152     }
1153 
isNetworkAvailable()1154     private boolean isNetworkAvailable() {
1155         if (mPhone.getServiceState().getDataRoaming()) {
1156             logd("Network unavailable because device is roaming.");
1157             return false;
1158         }
1159 
1160         if (!mPhone.getDataSettingsManager().isDataEnabledForReason(
1161                 TelephonyManager.DATA_ENABLED_REASON_USER)) {
1162             logd("Network unavailable because user data is disabled.");
1163             return false;
1164         }
1165 
1166         // TODO (b/251558673): Create a listener for data network type changed to dismiss
1167         //  notification and activity when the network is no longer available.
1168         switch (mPhone.getServiceState().getDataNetworkType()) {
1169             case TelephonyManager.NETWORK_TYPE_NR:
1170                 return true;
1171             case TelephonyManager.NETWORK_TYPE_LTE:
1172             case TelephonyManager.NETWORK_TYPE_LTE_CA:
1173                 return getCarrierConfigs().getBoolean(
1174                         CarrierConfigManager.KEY_PREMIUM_CAPABILITY_SUPPORTED_ON_LTE_BOOL);
1175         }
1176         return false;
1177     }
1178 
1179     /**
1180      * Returns the failure code {@link FailureCode} as a String.
1181      *
1182      * @param failureCode The failure code.
1183      * @return The failure code as a String.
1184      */
convertFailureCodeToString(@ailureCode int failureCode)1185     @NonNull private static String convertFailureCodeToString(@FailureCode int failureCode) {
1186         switch (failureCode) {
1187             case FAILURE_CODE_UNKNOWN: return "UNKNOWN";
1188             case FAILURE_CODE_CARRIER_URL_UNAVAILABLE: return "CARRIER_URL_UNAVAILABLE";
1189             case FAILURE_CODE_AUTHENTICATION_FAILED: return "AUTHENTICATION_FAILED";
1190             case FAILURE_CODE_PAYMENT_FAILED: return "PAYMENT_FAILED";
1191             case FAILURE_CODE_NO_USER_DATA: return "NO_USER_DATA";
1192             default:
1193                 return "UNKNOWN(" + failureCode + ")";
1194         }
1195     }
1196 
reportAnomaly(@onNull String uuid, @NonNull String log)1197     private void reportAnomaly(@NonNull String uuid, @NonNull String log) {
1198         loge(log);
1199         AnomalyReporter.reportAnomaly(UUID.fromString(uuid), log);
1200     }
1201 
logd(String s)1202     private void logd(String s) {
1203         Log.d(TAG + "-" + mPhone.getPhoneId(), s);
1204     }
1205 
loge(String s)1206     private void loge(String s) {
1207         Log.e(TAG + "-" + mPhone.getPhoneId(), s);
1208     }
1209 }
1210