/*
|
* Copyright (C) 2017 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.server.wifi;
|
|
import android.annotation.NonNull;
|
import android.content.BroadcastReceiver;
|
import android.content.Context;
|
import android.content.Intent;
|
import android.content.IntentFilter;
|
import android.database.ContentObserver;
|
import android.net.Uri;
|
import android.net.wifi.EAPConstants;
|
import android.net.wifi.WifiEnterpriseConfig;
|
import android.os.Handler;
|
import android.os.Looper;
|
import android.os.PersistableBundle;
|
import android.telephony.CarrierConfigManager;
|
import android.telephony.ImsiEncryptionInfo;
|
import android.telephony.SubscriptionInfo;
|
import android.telephony.SubscriptionManager;
|
import android.telephony.TelephonyManager;
|
import android.util.Base64;
|
import android.util.Log;
|
|
import java.io.FileDescriptor;
|
import java.io.PrintWriter;
|
import java.util.Arrays;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
/**
|
* Class for maintaining/caching carrier Wi-Fi network configurations.
|
*/
|
public class CarrierNetworkConfig {
|
private static final String TAG = "CarrierNetworkConfig";
|
|
private static final String NETWORK_CONFIG_SEPARATOR = ",";
|
private static final int ENCODED_SSID_INDEX = 0;
|
private static final int EAP_TYPE_INDEX = 1;
|
private static final int CONFIG_ELEMENT_SIZE = 2;
|
|
private static final Uri CONTENT_URI = Uri.parse("content://carrier_information/carrier");
|
|
private boolean mDbg = false;
|
|
private final Map<String, NetworkInfo> mCarrierNetworkMap;
|
private boolean mIsCarrierImsiEncryptionInfoAvailable = false;
|
private ImsiEncryptionInfo mLastImsiEncryptionInfo = null; // used for dumpsys only
|
|
/**
|
* Enable/disable verbose logging.
|
*/
|
public void enableVerboseLogging(int verbose) {
|
mDbg = verbose > 0;
|
}
|
|
public CarrierNetworkConfig(@NonNull Context context, @NonNull Looper looper,
|
@NonNull FrameworkFacade framework) {
|
mCarrierNetworkMap = new HashMap<>();
|
updateNetworkConfig(context);
|
|
// Monitor for carrier config changes.
|
IntentFilter filter = new IntentFilter();
|
filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
|
context.registerReceiver(new BroadcastReceiver() {
|
@Override
|
public void onReceive(Context context, Intent intent) {
|
updateNetworkConfig(context);
|
}
|
}, filter);
|
|
framework.registerContentObserver(context, CONTENT_URI, false,
|
new ContentObserver(new Handler(looper)) {
|
@Override
|
public void onChange(boolean selfChange) {
|
updateNetworkConfig(context);
|
}
|
});
|
}
|
|
/**
|
* @return true if the given SSID is associated with a carrier network
|
*/
|
public boolean isCarrierNetwork(String ssid) {
|
return mCarrierNetworkMap.containsKey(ssid);
|
}
|
|
/**
|
* @return the EAP type associated with a carrier AP, or -1 if the specified AP
|
* is not associated with a carrier network
|
*/
|
public int getNetworkEapType(String ssid) {
|
NetworkInfo info = mCarrierNetworkMap.get(ssid);
|
return info == null ? -1 : info.mEapType;
|
}
|
|
/**
|
* @return the name of carrier associated with a carrier AP, or null if the specified AP
|
* is not associated with a carrier network.
|
*/
|
public String getCarrierName(String ssid) {
|
NetworkInfo info = mCarrierNetworkMap.get(ssid);
|
return info == null ? null : info.mCarrierName;
|
}
|
|
/**
|
* @return True if carrier IMSI encryption info is available, False otherwise.
|
*/
|
public boolean isCarrierEncryptionInfoAvailable() {
|
return mIsCarrierImsiEncryptionInfoAvailable;
|
}
|
|
/**
|
* Verify whether carrier IMSI encryption info is available.
|
*
|
* @param context Current application context
|
*
|
* @return True if carrier IMSI encryption info is available, False otherwise.
|
*/
|
private boolean verifyCarrierImsiEncryptionInfoIsAvailable(Context context) {
|
// TODO(b/132188983): Inject this using WifiInjector
|
TelephonyManager telephonyManager =
|
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
if (telephonyManager == null) {
|
return false;
|
}
|
try {
|
mLastImsiEncryptionInfo = telephonyManager
|
.createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId())
|
.getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_WLAN);
|
if (mLastImsiEncryptionInfo == null) {
|
return false;
|
}
|
} catch (RuntimeException e) {
|
Log.e(TAG, "Failed to get imsi encryption info: " + e.getMessage());
|
return false;
|
}
|
|
return true;
|
}
|
|
/**
|
* Utility class for storing carrier network information.
|
*/
|
private static class NetworkInfo {
|
final int mEapType;
|
final String mCarrierName;
|
|
NetworkInfo(int eapType, String carrierName) {
|
mEapType = eapType;
|
mCarrierName = carrierName;
|
}
|
|
@Override
|
public String toString() {
|
return new StringBuffer("NetworkInfo: eap=").append(mEapType).append(
|
", carrier=").append(mCarrierName).toString();
|
}
|
}
|
|
/**
|
* Update the carrier network map based on the current carrier configuration of the active
|
* subscriptions.
|
*
|
* @param context Current application context
|
*/
|
private void updateNetworkConfig(Context context) {
|
mIsCarrierImsiEncryptionInfoAvailable = verifyCarrierImsiEncryptionInfoIsAvailable(context);
|
|
// Reset network map.
|
mCarrierNetworkMap.clear();
|
|
CarrierConfigManager carrierConfigManager =
|
(CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
|
if (carrierConfigManager == null) {
|
return;
|
}
|
|
SubscriptionManager subscriptionManager = (SubscriptionManager) context.getSystemService(
|
Context.TELEPHONY_SUBSCRIPTION_SERVICE);
|
if (subscriptionManager == null) {
|
return;
|
}
|
List<SubscriptionInfo> subInfoList = subscriptionManager.getActiveSubscriptionInfoList();
|
if (subInfoList == null) {
|
return;
|
}
|
|
// Process the carrier config for each active subscription.
|
for (SubscriptionInfo subInfo : subInfoList) {
|
CharSequence displayNameCs = subInfo.getDisplayName();
|
String displayNameStr = displayNameCs == null ? "" : displayNameCs.toString();
|
PersistableBundle bundle = carrierConfigManager.getConfigForSubId(
|
subInfo.getSubscriptionId());
|
processNetworkConfig(bundle, displayNameStr);
|
}
|
}
|
|
/**
|
* Process the carrier network config, the network config string is formatted as follow:
|
*
|
* "[Base64 Encoded SSID],[EAP Type]"
|
* Where EAP Type is the standard EAP method number, refer to
|
* http://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml for more info.
|
|
* @param carrierConfig The bundle containing the carrier configuration
|
* @param carrierName The display name of the associated carrier
|
*/
|
private void processNetworkConfig(PersistableBundle carrierConfig, String carrierName) {
|
if (carrierConfig == null) {
|
return;
|
}
|
String[] networkConfigs = carrierConfig.getStringArray(
|
CarrierConfigManager.KEY_CARRIER_WIFI_STRING_ARRAY);
|
if (mDbg) {
|
Log.v(TAG, "processNetworkConfig: networkConfigs="
|
+ Arrays.deepToString(networkConfigs));
|
}
|
if (networkConfigs == null) {
|
return;
|
}
|
|
for (String networkConfig : networkConfigs) {
|
String[] configArr = networkConfig.split(NETWORK_CONFIG_SEPARATOR);
|
if (configArr.length != CONFIG_ELEMENT_SIZE) {
|
Log.e(TAG, "Ignore invalid config: " + networkConfig);
|
continue;
|
}
|
try {
|
|
String ssid = new String(Base64.decode(
|
configArr[ENCODED_SSID_INDEX], Base64.NO_WRAP));
|
int eapType = parseEapType(Integer.parseInt(configArr[EAP_TYPE_INDEX]));
|
|
// Verify EAP type, must be a SIM based EAP type.
|
if (eapType == -1) {
|
Log.e(TAG, "Invalid EAP type: " + configArr[EAP_TYPE_INDEX]);
|
continue;
|
}
|
mCarrierNetworkMap.put(ssid, new NetworkInfo(eapType, carrierName));
|
} catch (NumberFormatException e) {
|
Log.e(TAG, "Failed to parse EAP type: '" + configArr[EAP_TYPE_INDEX] + "' "
|
+ e.getMessage());
|
} catch (IllegalArgumentException e) {
|
Log.e(TAG, "Failed to decode SSID: '" + configArr[ENCODED_SSID_INDEX] + "' "
|
+ e.getMessage());
|
}
|
}
|
}
|
|
/**
|
* Convert a standard SIM-based EAP type (SIM, AKA, AKA') to the internal EAP type as defined in
|
* {@link WifiEnterpriseConfig.Eap}. -1 will be returned if the given EAP type is not
|
* SIM-based.
|
*
|
* @return SIM-based EAP type as defined in {@link WifiEnterpriseConfig.Eap}, or -1 if not
|
* SIM-based EAP type
|
*/
|
private static int parseEapType(int eapType) {
|
if (eapType == EAPConstants.EAP_SIM) {
|
return WifiEnterpriseConfig.Eap.SIM;
|
} else if (eapType == EAPConstants.EAP_AKA) {
|
return WifiEnterpriseConfig.Eap.AKA;
|
} else if (eapType == EAPConstants.EAP_AKA_PRIME) {
|
return WifiEnterpriseConfig.Eap.AKA_PRIME;
|
}
|
return -1;
|
}
|
|
/** Dump state. */
|
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
pw.println(TAG + ": ");
|
pw.println("mCarrierNetworkMap=" + mCarrierNetworkMap);
|
pw.println("mIsCarrierImsiEncryptionInfoAvailable="
|
+ mIsCarrierImsiEncryptionInfoAvailable);
|
pw.println("mLastImsiEncryptionInfo=" + mLastImsiEncryptionInfo);
|
}
|
}
|