blob: 7d8ad4edb6cd50b9bd0fce7bd3daa73aca1d0f50 [file] [log] [blame]
/*
* Copyright (C) 2020 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.wifitrackerlib;
import static androidx.core.util.Preconditions.checkNotNull;
import static com.android.wifitrackerlib.Utils.getBestScanResultByLevel;
import static com.android.wifitrackerlib.WifiEntry.ConnectCallback.CONNECT_STATUS_FAILURE_UNKNOWN;
import android.content.Context;
import android.net.Uri;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.ProvisioningCallback;
import android.os.Handler;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.Pair;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.core.os.BuildCompat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* WifiEntry representation of an Online Sign-up entry, uniquely identified by FQDN.
*/
class OsuWifiEntry extends WifiEntry {
static final String KEY_PREFIX = "OsuWifiEntry:";
// Scan result list must be thread safe for generating the verbose scan summary
@NonNull private final List<ScanResult> mCurrentScanResults = new ArrayList<>();
@NonNull private final String mKey;
@NonNull private final Context mContext;
@NonNull private final OsuProvider mOsuProvider;
private String mSsid;
private String mOsuStatusString;
private boolean mIsAlreadyProvisioned = false;
private boolean mHasAddConfigUserRestriction = false;
private final UserManager mUserManager;
/**
* Create an OsuWifiEntry with the associated OsuProvider
*/
OsuWifiEntry(
@NonNull WifiTrackerInjector injector,
@NonNull Context context, @NonNull Handler callbackHandler,
@NonNull OsuProvider osuProvider,
@NonNull WifiManager wifiManager,
boolean forSavedNetworksPage) throws IllegalArgumentException {
super(callbackHandler, wifiManager, forSavedNetworksPage);
checkNotNull(osuProvider, "Cannot construct with null osuProvider!");
mContext = context;
mOsuProvider = osuProvider;
mKey = osuProviderToOsuWifiEntryKey(osuProvider);
mUserManager = injector.getUserManager();
if (BuildCompat.isAtLeastT() && mUserManager != null) {
mHasAddConfigUserRestriction = mUserManager.hasUserRestriction(
UserManager.DISALLOW_ADD_WIFI_CONFIG);
}
}
@Override
public String getKey() {
return mKey;
}
@Override
public synchronized String getTitle() {
final String friendlyName = mOsuProvider.getFriendlyName();
if (!TextUtils.isEmpty(friendlyName)) {
return friendlyName;
}
if (!TextUtils.isEmpty(mSsid)) {
return mSsid;
}
final Uri serverUri = mOsuProvider.getServerUri();
if (serverUri != null) {
return serverUri.toString();
}
return "";
}
@Override
public synchronized String getSummary(boolean concise) {
if (hasAdminRestrictions()) {
return mContext.getString(R.string.wifitrackerlib_admin_restricted_network);
}
// TODO(b/70983952): Add verbose summary
if (mOsuStatusString != null) {
return mOsuStatusString;
} else if (isAlreadyProvisioned()) {
return concise ? mContext.getString(R.string.wifitrackerlib_wifi_passpoint_expired)
: mContext.getString(
R.string.wifitrackerlib_tap_to_renew_subscription_and_connect);
} else {
return mContext.getString(R.string.wifitrackerlib_tap_to_sign_up);
}
}
@Override
public synchronized String getSsid() {
return mSsid;
}
@Override
public String getMacAddress() {
// TODO(b/70983952): Fill this method in in case we need the mac address for verbose logging
return null;
}
@Override
public synchronized boolean canConnect() {
//check user restriction and whether the network is already provisioned
if (hasAdminRestrictions()) {
return false;
}
return mLevel != WIFI_LEVEL_UNREACHABLE
&& getConnectedState() == CONNECTED_STATE_DISCONNECTED;
}
@Override
public synchronized void connect(@Nullable ConnectCallback callback) {
mConnectCallback = callback;
mWifiManager.stopRestrictingAutoJoinToSubscriptionId();
mWifiManager.startSubscriptionProvisioning(mOsuProvider, mContext.getMainExecutor(),
new OsuWifiEntryProvisioningCallback());
}
@WorkerThread
synchronized void updateScanResultInfo(@Nullable List<ScanResult> scanResults)
throws IllegalArgumentException {
if (scanResults == null) scanResults = new ArrayList<>();
mCurrentScanResults.clear();
mCurrentScanResults.addAll(scanResults);
final ScanResult bestScanResult = getBestScanResultByLevel(scanResults);
if (bestScanResult != null) {
mSsid = bestScanResult.SSID;
if (getConnectedState() == CONNECTED_STATE_DISCONNECTED) {
mLevel = mWifiManager.calculateSignalLevel(bestScanResult.level);
}
} else {
mLevel = WIFI_LEVEL_UNREACHABLE;
}
notifyOnUpdated();
}
@NonNull
static String osuProviderToOsuWifiEntryKey(@NonNull OsuProvider osuProvider) {
checkNotNull(osuProvider, "Cannot create key with null OsuProvider!");
return KEY_PREFIX + osuProvider.getFriendlyName() + ","
+ osuProvider.getServerUri().toString();
}
@WorkerThread
@Override
protected boolean connectionInfoMatches(@NonNull WifiInfo wifiInfo) {
return wifiInfo.isOsuAp() && TextUtils.equals(
wifiInfo.getPasspointProviderFriendlyName(), mOsuProvider.getFriendlyName());
}
@Override
protected String getScanResultDescription() {
// TODO(b/70983952): Fill this method in.
return "";
}
OsuProvider getOsuProvider() {
return mOsuProvider;
}
synchronized boolean isAlreadyProvisioned() {
return mIsAlreadyProvisioned;
}
synchronized void setAlreadyProvisioned(boolean isAlreadyProvisioned) {
mIsAlreadyProvisioned = isAlreadyProvisioned;
}
private boolean hasAdminRestrictions() {
if (mHasAddConfigUserRestriction && !mIsAlreadyProvisioned) {
return true;
}
return false;
}
class OsuWifiEntryProvisioningCallback extends ProvisioningCallback {
@Override
@MainThread public void onProvisioningFailure(int status) {
synchronized (OsuWifiEntry.this) {
if (TextUtils.equals(
mOsuStatusString, mContext.getString(
R.string.wifitrackerlib_osu_completing_sign_up))) {
mOsuStatusString =
mContext.getString(R.string.wifitrackerlib_osu_sign_up_failed);
} else {
mOsuStatusString =
mContext.getString(R.string.wifitrackerlib_osu_connect_failed);
}
}
final ConnectCallback connectCallback = mConnectCallback;
if (connectCallback != null) {
connectCallback.onConnectResult(CONNECT_STATUS_FAILURE_UNKNOWN);
}
notifyOnUpdated();
}
@Override
@MainThread public void onProvisioningStatus(int status) {
String newStatusString = null;
switch (status) {
case OSU_STATUS_AP_CONNECTING:
case OSU_STATUS_AP_CONNECTED:
case OSU_STATUS_SERVER_CONNECTING:
case OSU_STATUS_SERVER_VALIDATED:
case OSU_STATUS_SERVER_CONNECTED:
case OSU_STATUS_INIT_SOAP_EXCHANGE:
case OSU_STATUS_WAITING_FOR_REDIRECT_RESPONSE:
newStatusString = String.format(mContext.getString(
R.string.wifitrackerlib_osu_opening_provider),
getTitle());
break;
case OSU_STATUS_REDIRECT_RESPONSE_RECEIVED:
case OSU_STATUS_SECOND_SOAP_EXCHANGE:
case OSU_STATUS_THIRD_SOAP_EXCHANGE:
case OSU_STATUS_RETRIEVING_TRUST_ROOT_CERTS:
newStatusString = mContext.getString(
R.string.wifitrackerlib_osu_completing_sign_up);
break;
}
synchronized (OsuWifiEntry.this) {
boolean updated = !TextUtils.equals(mOsuStatusString, newStatusString);
mOsuStatusString = newStatusString;
if (updated) {
notifyOnUpdated();
}
}
}
@Override
@MainThread public void onProvisioningComplete() {
synchronized (OsuWifiEntry.this) {
mOsuStatusString = mContext.getString(R.string.wifitrackerlib_osu_sign_up_complete);
}
notifyOnUpdated();
PasspointConfiguration passpointConfig = mWifiManager
.getMatchingPasspointConfigsForOsuProviders(Collections.singleton(mOsuProvider))
.get(mOsuProvider);
final ConnectCallback connectCallback = mConnectCallback;
if (passpointConfig == null) {
// Failed to find the config we just provisioned
if (connectCallback != null) {
connectCallback.onConnectResult(CONNECT_STATUS_FAILURE_UNKNOWN);
}
return;
}
String uniqueId = passpointConfig.getUniqueId();
for (Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> pairing :
mWifiManager.getAllMatchingWifiConfigs(mWifiManager.getScanResults())) {
WifiConfiguration config = pairing.first;
if (TextUtils.equals(config.getKey(), uniqueId)) {
List<ScanResult> homeScans =
pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK);
List<ScanResult> roamingScans =
pairing.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK);
ScanResult bestScan;
if (homeScans != null && !homeScans.isEmpty()) {
bestScan = getBestScanResultByLevel(homeScans);
} else if (roamingScans != null && !roamingScans.isEmpty()) {
bestScan = getBestScanResultByLevel(roamingScans);
} else {
break;
}
config.SSID = "\"" + bestScan.SSID + "\"";
mWifiManager.connect(config, null /* ActionListener */);
return;
}
}
// Failed to find the network we provisioned for
if (connectCallback != null) {
connectCallback.onConnectResult(CONNECT_STATUS_FAILURE_UNKNOWN);
}
}
}
}