1 /* 2 * Copyright (C) 2020 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.wifitrackerlib; 18 19 import static androidx.core.util.Preconditions.checkNotNull; 20 21 import static com.android.wifitrackerlib.Utils.getBestScanResultByLevel; 22 import static com.android.wifitrackerlib.WifiEntry.ConnectCallback.CONNECT_STATUS_FAILURE_UNKNOWN; 23 24 import android.annotation.MainThread; 25 import android.content.Context; 26 import android.net.NetworkInfo; 27 import android.net.Uri; 28 import android.net.wifi.ScanResult; 29 import android.net.wifi.WifiConfiguration; 30 import android.net.wifi.WifiInfo; 31 import android.net.wifi.WifiManager; 32 import android.net.wifi.WifiNetworkScoreCache; 33 import android.net.wifi.hotspot2.OsuProvider; 34 import android.net.wifi.hotspot2.PasspointConfiguration; 35 import android.net.wifi.hotspot2.ProvisioningCallback; 36 import android.os.Handler; 37 import android.text.TextUtils; 38 import android.util.Pair; 39 40 import androidx.annotation.NonNull; 41 import androidx.annotation.Nullable; 42 import androidx.annotation.WorkerThread; 43 44 import java.util.ArrayList; 45 import java.util.Collections; 46 import java.util.List; 47 import java.util.Map; 48 49 /** 50 * WifiEntry representation of an Online Sign-up entry, uniquely identified by FQDN. 51 */ 52 class OsuWifiEntry extends WifiEntry { 53 static final String KEY_PREFIX = "OsuWifiEntry:"; 54 55 // Scan result list must be thread safe for generating the verbose scan summary 56 @NonNull private final List<ScanResult> mCurrentScanResults = new ArrayList<>(); 57 58 @NonNull private final String mKey; 59 @NonNull private final Context mContext; 60 @NonNull private final OsuProvider mOsuProvider; 61 private String mSsid; 62 private String mOsuStatusString; 63 private boolean mIsAlreadyProvisioned = false; 64 65 /** 66 * Create an OsuWifiEntry with the associated OsuProvider 67 */ OsuWifiEntry(@onNull Context context, @NonNull Handler callbackHandler, @NonNull OsuProvider osuProvider, @NonNull WifiManager wifiManager, @NonNull WifiNetworkScoreCache scoreCache, boolean forSavedNetworksPage)68 OsuWifiEntry(@NonNull Context context, @NonNull Handler callbackHandler, 69 @NonNull OsuProvider osuProvider, 70 @NonNull WifiManager wifiManager, 71 @NonNull WifiNetworkScoreCache scoreCache, 72 boolean forSavedNetworksPage) throws IllegalArgumentException { 73 super(callbackHandler, wifiManager, scoreCache, forSavedNetworksPage); 74 75 checkNotNull(osuProvider, "Cannot construct with null osuProvider!"); 76 77 mContext = context; 78 mOsuProvider = osuProvider; 79 mKey = osuProviderToOsuWifiEntryKey(osuProvider); 80 } 81 82 @Override getKey()83 public String getKey() { 84 return mKey; 85 } 86 87 @Override getTitle()88 public synchronized String getTitle() { 89 final String friendlyName = mOsuProvider.getFriendlyName(); 90 if (!TextUtils.isEmpty(friendlyName)) { 91 return friendlyName; 92 } 93 if (!TextUtils.isEmpty(mSsid)) { 94 return mSsid; 95 } 96 final Uri serverUri = mOsuProvider.getServerUri(); 97 if (serverUri != null) { 98 return serverUri.toString(); 99 } 100 return ""; 101 } 102 103 @Override getSummary(boolean concise)104 public synchronized String getSummary(boolean concise) { 105 // TODO(b/70983952): Add verbose summary 106 if (mOsuStatusString != null) { 107 return mOsuStatusString; 108 } else if (isAlreadyProvisioned()) { 109 return concise ? mContext.getString(R.string.wifitrackerlib_wifi_passpoint_expired) 110 : mContext.getString( 111 R.string.wifitrackerlib_tap_to_renew_subscription_and_connect); 112 } else { 113 return mContext.getString(R.string.wifitrackerlib_tap_to_sign_up); 114 } 115 } 116 117 @Override getSsid()118 public synchronized String getSsid() { 119 return mSsid; 120 } 121 122 @Override getMacAddress()123 public String getMacAddress() { 124 // TODO(b/70983952): Fill this method in in case we need the mac address for verbose logging 125 return null; 126 } 127 128 @Override canConnect()129 public synchronized boolean canConnect() { 130 return mLevel != WIFI_LEVEL_UNREACHABLE 131 && getConnectedState() == CONNECTED_STATE_DISCONNECTED; 132 } 133 134 @Override connect(@ullable ConnectCallback callback)135 public synchronized void connect(@Nullable ConnectCallback callback) { 136 mConnectCallback = callback; 137 mWifiManager.stopRestrictingAutoJoinToSubscriptionId(); 138 mWifiManager.startSubscriptionProvisioning(mOsuProvider, mContext.getMainExecutor(), 139 new OsuWifiEntryProvisioningCallback()); 140 } 141 142 @WorkerThread updateScanResultInfo(@ullable List<ScanResult> scanResults)143 synchronized void updateScanResultInfo(@Nullable List<ScanResult> scanResults) 144 throws IllegalArgumentException { 145 if (scanResults == null) scanResults = new ArrayList<>(); 146 147 mCurrentScanResults.clear(); 148 mCurrentScanResults.addAll(scanResults); 149 150 final ScanResult bestScanResult = getBestScanResultByLevel(scanResults); 151 if (bestScanResult != null) { 152 mSsid = bestScanResult.SSID; 153 if (getConnectedState() == CONNECTED_STATE_DISCONNECTED) { 154 mLevel = mWifiManager.calculateSignalLevel(bestScanResult.level); 155 } 156 } else { 157 mLevel = WIFI_LEVEL_UNREACHABLE; 158 } 159 notifyOnUpdated(); 160 } 161 162 @NonNull osuProviderToOsuWifiEntryKey(@onNull OsuProvider osuProvider)163 static String osuProviderToOsuWifiEntryKey(@NonNull OsuProvider osuProvider) { 164 checkNotNull(osuProvider, "Cannot create key with null OsuProvider!"); 165 return KEY_PREFIX + osuProvider.getFriendlyName() + "," 166 + osuProvider.getServerUri().toString(); 167 } 168 169 @WorkerThread 170 @Override connectionInfoMatches(@onNull WifiInfo wifiInfo, @NonNull NetworkInfo networkInfo)171 protected boolean connectionInfoMatches(@NonNull WifiInfo wifiInfo, 172 @NonNull NetworkInfo networkInfo) { 173 return wifiInfo.isOsuAp() && TextUtils.equals( 174 wifiInfo.getPasspointProviderFriendlyName(), mOsuProvider.getFriendlyName()); 175 } 176 177 @Override getScanResultDescription()178 protected String getScanResultDescription() { 179 // TODO(b/70983952): Fill this method in. 180 return ""; 181 } 182 getOsuProvider()183 OsuProvider getOsuProvider() { 184 return mOsuProvider; 185 } 186 isAlreadyProvisioned()187 synchronized boolean isAlreadyProvisioned() { 188 return mIsAlreadyProvisioned; 189 } 190 setAlreadyProvisioned(boolean isAlreadyProvisioned)191 synchronized void setAlreadyProvisioned(boolean isAlreadyProvisioned) { 192 mIsAlreadyProvisioned = isAlreadyProvisioned; 193 } 194 195 class OsuWifiEntryProvisioningCallback extends ProvisioningCallback { 196 @Override onProvisioningFailure(int status)197 @MainThread public void onProvisioningFailure(int status) { 198 synchronized (OsuWifiEntry.this) { 199 if (TextUtils.equals( 200 mOsuStatusString, mContext.getString( 201 R.string.wifitrackerlib_osu_completing_sign_up))) { 202 mOsuStatusString = 203 mContext.getString(R.string.wifitrackerlib_osu_sign_up_failed); 204 } else { 205 mOsuStatusString = 206 mContext.getString(R.string.wifitrackerlib_osu_connect_failed); 207 } 208 } 209 final ConnectCallback connectCallback = mConnectCallback; 210 if (connectCallback != null) { 211 connectCallback.onConnectResult(CONNECT_STATUS_FAILURE_UNKNOWN); 212 } 213 notifyOnUpdated(); 214 } 215 216 @Override onProvisioningStatus(int status)217 @MainThread public void onProvisioningStatus(int status) { 218 String newStatusString = null; 219 switch (status) { 220 case OSU_STATUS_AP_CONNECTING: 221 case OSU_STATUS_AP_CONNECTED: 222 case OSU_STATUS_SERVER_CONNECTING: 223 case OSU_STATUS_SERVER_VALIDATED: 224 case OSU_STATUS_SERVER_CONNECTED: 225 case OSU_STATUS_INIT_SOAP_EXCHANGE: 226 case OSU_STATUS_WAITING_FOR_REDIRECT_RESPONSE: 227 newStatusString = String.format(mContext.getString( 228 R.string.wifitrackerlib_osu_opening_provider), 229 getTitle()); 230 break; 231 case OSU_STATUS_REDIRECT_RESPONSE_RECEIVED: 232 case OSU_STATUS_SECOND_SOAP_EXCHANGE: 233 case OSU_STATUS_THIRD_SOAP_EXCHANGE: 234 case OSU_STATUS_RETRIEVING_TRUST_ROOT_CERTS: 235 newStatusString = mContext.getString( 236 R.string.wifitrackerlib_osu_completing_sign_up); 237 break; 238 } 239 synchronized (OsuWifiEntry.this) { 240 boolean updated = !TextUtils.equals(mOsuStatusString, newStatusString); 241 mOsuStatusString = newStatusString; 242 if (updated) { 243 notifyOnUpdated(); 244 } 245 } 246 } 247 248 @Override onProvisioningComplete()249 @MainThread public void onProvisioningComplete() { 250 synchronized (OsuWifiEntry.this) { 251 mOsuStatusString = mContext.getString(R.string.wifitrackerlib_osu_sign_up_complete); 252 } 253 notifyOnUpdated(); 254 255 PasspointConfiguration passpointConfig = mWifiManager 256 .getMatchingPasspointConfigsForOsuProviders(Collections.singleton(mOsuProvider)) 257 .get(mOsuProvider); 258 final ConnectCallback connectCallback = mConnectCallback; 259 if (passpointConfig == null) { 260 // Failed to find the config we just provisioned 261 if (connectCallback != null) { 262 connectCallback.onConnectResult(CONNECT_STATUS_FAILURE_UNKNOWN); 263 } 264 return; 265 } 266 String uniqueId = passpointConfig.getUniqueId(); 267 for (Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> pairing : 268 mWifiManager.getAllMatchingWifiConfigs(mWifiManager.getScanResults())) { 269 WifiConfiguration config = pairing.first; 270 if (TextUtils.equals(config.getKey(), uniqueId)) { 271 List<ScanResult> homeScans = 272 pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK); 273 List<ScanResult> roamingScans = 274 pairing.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK); 275 ScanResult bestScan; 276 if (homeScans != null && !homeScans.isEmpty()) { 277 bestScan = getBestScanResultByLevel(homeScans); 278 } else if (roamingScans != null && !roamingScans.isEmpty()) { 279 bestScan = getBestScanResultByLevel(roamingScans); 280 } else { 281 break; 282 } 283 config.SSID = "\"" + bestScan.SSID + "\""; 284 mWifiManager.connect(config, null /* ActionListener */); 285 return; 286 } 287 } 288 289 // Failed to find the network we provisioned for 290 if (connectCallback != null) { 291 connectCallback.onConnectResult(CONNECT_STATUS_FAILURE_UNKNOWN); 292 } 293 } 294 } 295 } 296