1/*
2 * Copyright (C) 2017 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
17package com.android.server.wifi;
18
19import android.annotation.NonNull;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.database.ContentObserver;
25import android.net.Uri;
26import android.net.wifi.EAPConstants;
27import android.net.wifi.WifiEnterpriseConfig;
28import android.os.Handler;
29import android.os.Looper;
30import android.os.PersistableBundle;
31import android.telephony.CarrierConfigManager;
32import android.telephony.ImsiEncryptionInfo;
33import android.telephony.SubscriptionInfo;
34import android.telephony.SubscriptionManager;
35import android.telephony.TelephonyManager;
36import android.util.Base64;
37import android.util.Log;
38
39import java.util.HashMap;
40import java.util.List;
41import java.util.Map;
42
43/**
44 * Class for maintaining/caching carrier Wi-Fi network configurations.
45 */
46public class CarrierNetworkConfig {
47    private static final String TAG = "CarrierNetworkConfig";
48
49    private static final String NETWORK_CONFIG_SEPARATOR = ",";
50    private static final int ENCODED_SSID_INDEX = 0;
51    private static final int EAP_TYPE_INDEX = 1;
52    private static final int CONFIG_ELEMENT_SIZE = 2;
53    private static final Uri CONTENT_URI = Uri.parse("content://carrier_information/carrier");
54
55    private final Map<String, NetworkInfo> mCarrierNetworkMap;
56    private boolean mIsCarrierImsiEncryptionInfoAvailable = false;
57
58    public CarrierNetworkConfig(@NonNull Context context, @NonNull Looper looper,
59            @NonNull FrameworkFacade framework) {
60        mCarrierNetworkMap = new HashMap<>();
61        updateNetworkConfig(context);
62
63        // Monitor for carrier config changes.
64        IntentFilter filter = new IntentFilter();
65        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
66        context.registerReceiver(new BroadcastReceiver() {
67            @Override
68            public void onReceive(Context context, Intent intent) {
69                updateNetworkConfig(context);
70            }
71        }, filter);
72
73        framework.registerContentObserver(context, CONTENT_URI, false,
74                new ContentObserver(new Handler(looper)) {
75                @Override
76                public void onChange(boolean selfChange) {
77                    updateNetworkConfig(context);
78                }
79            });
80    }
81
82    /**
83     * @return true if the given SSID is associated with a carrier network
84     */
85    public boolean isCarrierNetwork(String ssid) {
86        return mCarrierNetworkMap.containsKey(ssid);
87    }
88
89    /**
90     * @return the EAP type associated with a carrier AP, or -1 if the specified AP
91     * is not associated with a carrier network
92     */
93    public int getNetworkEapType(String ssid) {
94        NetworkInfo info = mCarrierNetworkMap.get(ssid);
95        return info == null ? -1 : info.mEapType;
96    }
97
98    /**
99     * @return the name of carrier associated with a carrier AP, or null if the specified AP
100     * is not associated with a carrier network.
101     */
102    public String getCarrierName(String ssid) {
103        NetworkInfo info = mCarrierNetworkMap.get(ssid);
104        return info == null ? null : info.mCarrierName;
105    }
106
107    /**
108     * @return True if carrier IMSI encryption info is available, False otherwise.
109     */
110    public boolean isCarrierEncryptionInfoAvailable() {
111        return mIsCarrierImsiEncryptionInfoAvailable;
112    }
113
114    /**
115     * Verify whether carrier IMSI encryption info is available.
116     *
117     * @param context Current application context
118     *
119     * @return True if carrier IMSI encryption info is available, False otherwise.
120     */
121    private boolean verifyCarrierImsiEncryptionInfoIsAvailable(Context context) {
122        TelephonyManager telephonyManager =
123                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
124        if (telephonyManager == null) {
125            return false;
126        }
127        try {
128            ImsiEncryptionInfo imsiEncryptionInfo = telephonyManager
129                    .getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_WLAN);
130            if (imsiEncryptionInfo == null) {
131                return false;
132            }
133        } catch (RuntimeException e) {
134            Log.e(TAG, "Failed to get imsi encryption info: " + e.getMessage());
135            return false;
136        }
137
138        return true;
139    }
140
141    /**
142     * Utility class for storing carrier network information.
143     */
144    private static class NetworkInfo {
145        final int mEapType;
146        final String mCarrierName;
147
148        NetworkInfo(int eapType, String carrierName) {
149            mEapType = eapType;
150            mCarrierName = carrierName;
151        }
152    }
153
154    /**
155     * Update the carrier network map based on the current carrier configuration of the active
156     * subscriptions.
157     *
158     * @param context Current application context
159     */
160    private void updateNetworkConfig(Context context) {
161        mIsCarrierImsiEncryptionInfoAvailable = verifyCarrierImsiEncryptionInfoIsAvailable(context);
162
163        // Reset network map.
164        mCarrierNetworkMap.clear();
165
166        CarrierConfigManager carrierConfigManager =
167                (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
168        if (carrierConfigManager == null) {
169            return;
170        }
171
172        SubscriptionManager subscriptionManager = (SubscriptionManager) context.getSystemService(
173                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
174        if (subscriptionManager == null) {
175            return;
176        }
177        List<SubscriptionInfo> subInfoList = subscriptionManager.getActiveSubscriptionInfoList();
178        if (subInfoList == null) {
179            return;
180        }
181
182        // Process the carrier config for each active subscription.
183        for (SubscriptionInfo subInfo : subInfoList) {
184            processNetworkConfig(
185                    carrierConfigManager.getConfigForSubId(subInfo.getSubscriptionId()),
186                    subInfo.getDisplayName().toString());
187        }
188    }
189
190    /**
191     * Process the carrier network config, the network config string is formatted as follow:
192     *
193     * "[Base64 Encoded SSID],[EAP Type]"
194     * Where EAP Type is the standard EAP method number, refer to
195     * http://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml for more info.
196
197     * @param carrierConfig The bundle containing the carrier configuration
198     * @param carrierName The display name of the associated carrier
199     */
200    private void processNetworkConfig(PersistableBundle carrierConfig, String carrierName) {
201        if (carrierConfig == null) {
202            return;
203        }
204        String[] networkConfigs = carrierConfig.getStringArray(
205                CarrierConfigManager.KEY_CARRIER_WIFI_STRING_ARRAY);
206        if (networkConfigs == null) {
207            return;
208        }
209
210        for (String networkConfig : networkConfigs) {
211            String[] configArr = networkConfig.split(NETWORK_CONFIG_SEPARATOR);
212            if (configArr.length != CONFIG_ELEMENT_SIZE) {
213                Log.e(TAG, "Ignore invalid config: " + networkConfig);
214                continue;
215            }
216            try {
217                String ssid = new String(Base64.decode(
218                        configArr[ENCODED_SSID_INDEX], Base64.DEFAULT));
219                int eapType = parseEapType(Integer.parseInt(configArr[EAP_TYPE_INDEX]));
220                // Verify EAP type, must be a SIM based EAP type.
221                if (eapType == -1) {
222                    Log.e(TAG, "Invalid EAP type: " + configArr[EAP_TYPE_INDEX]);
223                    continue;
224                }
225                mCarrierNetworkMap.put(ssid, new NetworkInfo(eapType, carrierName));
226            } catch (NumberFormatException e) {
227                Log.e(TAG, "Failed to parse EAP type: " + e.getMessage());
228            } catch (IllegalArgumentException e) {
229                Log.e(TAG, "Failed to decode SSID: " + e.getMessage());
230            }
231        }
232    }
233
234    /**
235     * Convert a standard SIM-based EAP type (SIM, AKA, AKA') to the internal EAP type as defined in
236     * {@link WifiEnterpriseConfig.Eap}. -1 will be returned if the given EAP type is not
237     * SIM-based.
238     *
239     * @return SIM-based EAP type as defined in {@link WifiEnterpriseConfig.Eap}, or -1 if not
240     * SIM-based EAP type
241     */
242    private static int parseEapType(int eapType) {
243        if (eapType == EAPConstants.EAP_SIM) {
244            return WifiEnterpriseConfig.Eap.SIM;
245        } else if (eapType == EAPConstants.EAP_AKA) {
246            return WifiEnterpriseConfig.Eap.AKA;
247        } else if (eapType == EAPConstants.EAP_AKA_PRIME) {
248            return WifiEnterpriseConfig.Eap.AKA_PRIME;
249        }
250        return -1;
251    }
252}
253