• 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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.PersistableBundle;
22 import android.provider.DeviceConfig;
23 import android.telephony.AnomalyReporter;
24 import android.telephony.CarrierConfigManager;
25 import android.telephony.TelephonyManager;
26 import android.util.Log;
27 
28 import com.android.internal.telephony.Phone;
29 import com.android.libraries.entitlement.CarrierConfig;
30 import com.android.libraries.entitlement.ServiceEntitlement;
31 import com.android.libraries.entitlement.ServiceEntitlementException;
32 import com.android.libraries.entitlement.ServiceEntitlementRequest;
33 
34 import org.json.JSONException;
35 import org.json.JSONObject;
36 
37 import java.util.UUID;
38 
39 /**
40  * Premium network entitlement API class to check the premium network slice entitlement result
41  * from carrier API over the network.
42  */
43 public class PremiumNetworkEntitlementApi {
44     private static final String TAG = "PremiumNwEntitlementApi";
45     private static final String ENTITLEMENT_STATUS_KEY = "EntitlementStatus";
46     private static final String PROVISION_STATUS_KEY = "ProvStatus";
47     private static final String SERVICE_FLOW_URL_KEY = "ServiceFlow_URL";
48     private static final String SERVICE_FLOW_USERDATA_KEY = "ServiceFlow_UserData";
49     private static final String SERVICE_FLOW_CONTENTS_TYPE_KEY = "ServiceFlow_ContentsType";
50     private static final String DEFAULT_EAP_AKA_RESPONSE = "Default EAP AKA response";
51     /**
52      * UUID to report an anomaly if an unexpected error is received during entitlement check.
53      */
54     private static final String UUID_ENTITLEMENT_CHECK_UNEXPECTED_ERROR =
55             "f2b0661a-9114-4b1b-9add-a8d338f9c054";
56 
57     /**
58      * Experiment flag to enable bypassing EAP-AKA authentication for Slice Purchase activities.
59      * The device will accept any challenge from the entitlement server and return a predefined
60      * string as a response.
61      *
62      * This flag should be enabled for testing only.
63      */
64     public static final String BYPASS_EAP_AKA_AUTH_FOR_SLICE_PURCHASE_ENABLED =
65             "bypass_eap_aka_auth_for_slice_purchase_enabled";
66 
67     @NonNull private final Phone mPhone;
68     @NonNull private final ServiceEntitlement mServiceEntitlement;
69 
PremiumNetworkEntitlementApi(@onNull Phone phone, @NonNull PersistableBundle carrierConfig)70     public PremiumNetworkEntitlementApi(@NonNull Phone phone,
71             @NonNull PersistableBundle carrierConfig) {
72         mPhone = phone;
73         if (isBypassEapAkaAuthForSlicePurchaseEnabled()) {
74             mServiceEntitlement =
75                     new ServiceEntitlement(
76                             mPhone.getContext(),
77                             getEntitlementServerCarrierConfig(carrierConfig),
78                             mPhone.getSubId(),
79                             true,
80                             DEFAULT_EAP_AKA_RESPONSE);
81         } else {
82             mServiceEntitlement =
83                     new ServiceEntitlement(
84                             mPhone.getContext(),
85                             getEntitlementServerCarrierConfig(carrierConfig),
86                             mPhone.getSubId());
87         }
88     }
89 
90     /**
91      * Returns premium network slice entitlement check result from carrier API (over network),
92      * or {@code null} on unrecoverable network issue or malformed server response.
93      * This is blocking call sending HTTP request and should not be called on main thread.
94      */
checkEntitlementStatus( @elephonyManager.PremiumCapability int capability)95     @Nullable public PremiumNetworkEntitlementResponse checkEntitlementStatus(
96             @TelephonyManager.PremiumCapability int capability) {
97         Log.d(TAG, "checkEntitlementStatus subId=" + mPhone.getSubId());
98         ServiceEntitlementRequest.Builder requestBuilder = ServiceEntitlementRequest.builder();
99         // Set fake device info to avoid leaking
100         requestBuilder.setTerminalVendor("vendorX");
101         requestBuilder.setTerminalModel("modelY");
102         requestBuilder.setTerminalSoftwareVersion("versionZ");
103         requestBuilder.setAcceptContentType(ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON);
104         requestBuilder.setBoostType(getBoostTypeFromPremiumCapability(capability));
105         ServiceEntitlementRequest request = requestBuilder.build();
106         PremiumNetworkEntitlementResponse premiumNetworkEntitlementResponse =
107                 new PremiumNetworkEntitlementResponse();
108 
109         String response = null;
110         try {
111             response = mServiceEntitlement.queryEntitlementStatus(
112                     ServiceEntitlement.APP_DATA_PLAN_BOOST,
113                     request);
114         } catch (ServiceEntitlementException e) {
115             Log.e(TAG, "queryEntitlementStatus failed", e);
116             reportAnomaly(UUID_ENTITLEMENT_CHECK_UNEXPECTED_ERROR,
117                     "checkEntitlementStatus failed with ServiceEntitlementException");
118         }
119         if (response == null) {
120             return null;
121         }
122         try {
123             JSONObject jsonAuthResponse = new JSONObject(response);
124             if (jsonAuthResponse.has(ServiceEntitlement.APP_DATA_PLAN_BOOST)) {
125                 JSONObject jsonToken = jsonAuthResponse.getJSONObject(
126                         ServiceEntitlement.APP_DATA_PLAN_BOOST);
127                 if (jsonToken.has(ENTITLEMENT_STATUS_KEY)) {
128                     String entitlementStatus = jsonToken.getString(ENTITLEMENT_STATUS_KEY);
129                     if (entitlementStatus == null) {
130                         return null;
131                     }
132                     premiumNetworkEntitlementResponse.mEntitlementStatus =
133                             Integer.parseInt(entitlementStatus);
134                 }
135                 if (jsonToken.has(PROVISION_STATUS_KEY)) {
136                     String provisionStatus = jsonToken.getString(PROVISION_STATUS_KEY);
137                     if (provisionStatus != null) {
138                         premiumNetworkEntitlementResponse.mProvisionStatus =
139                                 Integer.parseInt(provisionStatus);
140                     }
141                 }
142                 if (jsonToken.has(SERVICE_FLOW_URL_KEY)) {
143                     premiumNetworkEntitlementResponse.mServiceFlowURL =
144                             jsonToken.getString(SERVICE_FLOW_URL_KEY);
145                 }
146                 if (jsonToken.has(SERVICE_FLOW_USERDATA_KEY)) {
147                     premiumNetworkEntitlementResponse.mServiceFlowUserData =
148                             jsonToken.getString(SERVICE_FLOW_USERDATA_KEY);
149                 }
150                 if (jsonToken.has(SERVICE_FLOW_CONTENTS_TYPE_KEY)) {
151                     premiumNetworkEntitlementResponse.mServiceFlowContentsType =
152                             jsonToken.getString(SERVICE_FLOW_CONTENTS_TYPE_KEY);
153                 }
154             } else {
155                 Log.e(TAG, "queryEntitlementStatus failed with no app");
156             }
157         } catch (JSONException e) {
158             Log.e(TAG, "queryEntitlementStatus failed", e);
159             reportAnomaly(UUID_ENTITLEMENT_CHECK_UNEXPECTED_ERROR,
160                     "checkEntitlementStatus failed with JSONException");
161         } catch (NumberFormatException e) {
162             Log.e(TAG, "queryEntitlementStatus failed", e);
163             reportAnomaly(UUID_ENTITLEMENT_CHECK_UNEXPECTED_ERROR,
164                     "checkEntitlementStatus failed with NumberFormatException");
165         }
166         Log.d(TAG, "queryEntitlementStatus succeeded with response: "
167                 + premiumNetworkEntitlementResponse);
168         return premiumNetworkEntitlementResponse;
169     }
170 
reportAnomaly(@onNull String uuid, @NonNull String log)171     private void reportAnomaly(@NonNull String uuid, @NonNull String log) {
172         AnomalyReporter.reportAnomaly(UUID.fromString(uuid), log);
173     }
174 
175     /**
176      * Returns entitlement server url from the given carrier configs or a default empty string
177      * if it is not available.
178      */
getEntitlementServerUrl( @onNull PersistableBundle carrierConfig)179     @NonNull public static String getEntitlementServerUrl(
180             @NonNull PersistableBundle carrierConfig) {
181         return carrierConfig.getString(
182                 CarrierConfigManager.ImsServiceEntitlement.KEY_ENTITLEMENT_SERVER_URL_STRING,
183                 "");
184     }
185 
getEntitlementServerCarrierConfig( @onNull PersistableBundle carrierConfig)186     @NonNull private CarrierConfig getEntitlementServerCarrierConfig(
187             @NonNull PersistableBundle carrierConfig) {
188         String entitlementServiceUrl = getEntitlementServerUrl(carrierConfig);
189         return CarrierConfig.builder().setServerUrl(entitlementServiceUrl).build();
190     }
191 
isBypassEapAkaAuthForSlicePurchaseEnabled()192     private boolean isBypassEapAkaAuthForSlicePurchaseEnabled() {
193         return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TELEPHONY,
194                 BYPASS_EAP_AKA_AUTH_FOR_SLICE_PURCHASE_ENABLED, false);
195     }
196 
getBoostTypeFromPremiumCapability( @elephonyManager.PremiumCapability int capability)197     @NonNull private String getBoostTypeFromPremiumCapability(
198             @TelephonyManager.PremiumCapability int capability) {
199         if (capability == TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY) {
200             return "0" /* REALTIME_INTERACTIVE_TRAFFIC */;
201         }
202         return "";
203     }
204 }
205