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