/* * Copyright (C) 2010 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 static android.app.AppOpsManager.MODE_ALLOWED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_GENERIC; import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_NO_CHANNEL; import static android.net.wifi.WifiManager.SAP_START_FAILURE_NO_CHANNEL; import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING; import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED; import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_LOCAL_ONLY; import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_LONG_LIVED; import static com.android.server.wifi.ClientModeImpl.RESET_SIM_REASON_DEFAULT_DATA_SIM_CHANGED; import static com.android.server.wifi.ClientModeImpl.RESET_SIM_REASON_SIM_INSERTED; import static com.android.server.wifi.ClientModeImpl.RESET_SIM_REASON_SIM_REMOVED; import static com.android.server.wifi.SelfRecovery.REASON_API_CALL; import static com.android.server.wifi.WifiConfigurationUtil.addSecurityTypeToNetworkId; import static com.android.server.wifi.WifiConfigurationUtil.convertWifiInfoSecurityTypeToWifiConfiguration; import static com.android.server.wifi.WifiConfigurationUtil.removeSecurityTypeFromNetworkId; import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_VERBOSE_LOGGING_ENABLED; import android.Manifest; import android.annotation.CheckResult; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.DhcpInfo; import android.net.DhcpResultsParcelable; import android.net.InetAddresses; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkStack; import android.net.Uri; import android.net.ip.IpClientUtil; import android.net.wifi.CoexUnsafeChannel; import android.net.wifi.IActionListener; import android.net.wifi.ICoexCallback; import android.net.wifi.IDppCallback; import android.net.wifi.ILocalOnlyHotspotCallback; import android.net.wifi.INetworkRequestMatchCallback; import android.net.wifi.IOnWifiActivityEnergyInfoListener; import android.net.wifi.IOnWifiUsabilityStatsListener; import android.net.wifi.IScanResultsCallback; import android.net.wifi.ISoftApCallback; import android.net.wifi.ISubsystemRestartCallback; import android.net.wifi.ISuggestionConnectionStatusListener; import android.net.wifi.ISuggestionUserApprovalStatusListener; import android.net.wifi.ITrafficStateCallback; import android.net.wifi.IWifiConnectedNetworkScorer; import android.net.wifi.IWifiVerboseLoggingStatusChangedListener; import android.net.wifi.ScanResult; import android.net.wifi.SoftApCapability; import android.net.wifi.SoftApConfiguration; import android.net.wifi.SoftApInfo; import android.net.wifi.WifiAnnotations.WifiStandard; import android.net.wifi.WifiAvailableChannel; import android.net.wifi.WifiClient; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.AddNetworkResult; import android.net.wifi.WifiManager.CoexRestriction; import android.net.wifi.WifiManager.DeviceMobilityState; import android.net.wifi.WifiManager.LocalOnlyHotspotCallback; import android.net.wifi.WifiManager.SapClientBlockedReason; import android.net.wifi.WifiManager.SapStartFailure; import android.net.wifi.WifiManager.SuggestionConnectionStatusListener; import android.net.wifi.WifiManager.WifiApState; import android.net.wifi.WifiNetworkSuggestion; import android.net.wifi.WifiScanner; import android.net.wifi.hotspot2.IProvisioningCallback; import android.net.wifi.hotspot2.OsuProvider; import android.net.wifi.hotspot2.PasspointConfiguration; import android.os.AsyncTask; import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.HandlerExecutor; import android.os.IBinder; import android.os.Looper; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.PowerManager; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; import android.os.connectivity.WifiActivityEnergyInfo; import android.provider.Settings; import android.telephony.CarrierConfigManager; import android.telephony.PhoneStateListener; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; import androidx.annotation.RequiresApi; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.ParceledListSlice; import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.Inet4AddressUtils; import com.android.server.wifi.coex.CoexManager; import com.android.server.wifi.hotspot2.PasspointManager; import com.android.server.wifi.hotspot2.PasspointProvider; import com.android.server.wifi.proto.nano.WifiMetricsProto.UserActionEvent; import com.android.server.wifi.util.ActionListenerWrapper; import com.android.server.wifi.util.ApConfigUtil; import com.android.server.wifi.util.GeneralUtil.Mutable; import com.android.server.wifi.util.LastCallerInfoManager; import com.android.server.wifi.util.RssiUtil; import com.android.server.wifi.util.ScanResultUtil; import com.android.server.wifi.util.WifiPermissionsUtil; import com.android.wifi.resources.R; import java.io.BufferedReader; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.net.Inet4Address; import java.net.InetAddress; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.cert.CertPath; import java.security.cert.CertPathValidator; import java.security.cert.CertificateFactory; import java.security.cert.PKIXParameters; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; /** * WifiService handles remote WiFi operation requests by implementing * the IWifiManager interface. */ public class WifiServiceImpl extends BaseWifiService { private static final String TAG = "WifiService"; private static final boolean VDBG = false; /** Max wait time for posting blocking runnables */ private static final int RUN_WITH_SCISSORS_TIMEOUT_MILLIS = 4000; @VisibleForTesting static final int AUTO_DISABLE_SHOW_KEY_COUNTDOWN_MILLIS = 24 * 60 * 60 * 1000; private final ActiveModeWarden mActiveModeWarden; private final ScanRequestProxy mScanRequestProxy; private final Context mContext; private final FrameworkFacade mFacade; private final Clock mClock; private final PowerManager mPowerManager; private final AppOpsManager mAppOps; private final UserManager mUserManager; private final WifiCountryCode mCountryCode; /** Polls traffic stats and notifies clients */ private final WifiTrafficPoller mWifiTrafficPoller; /** Tracks the persisted states for wi-fi & airplane mode */ private final WifiSettingsStore mSettingsStore; /** Logs connection events and some general router and scan stats */ private final WifiMetrics mWifiMetrics; private final WifiInjector mWifiInjector; /** Backup/Restore Module */ private final WifiBackupRestore mWifiBackupRestore; private final SoftApBackupRestore mSoftApBackupRestore; private final CoexManager mCoexManager; private final WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager; private final WifiConfigManager mWifiConfigManager; private final PasspointManager mPasspointManager; private final WifiLog mLog; private final WifiConnectivityManager mWifiConnectivityManager; private final ConnectHelper mConnectHelper; private final WifiGlobals mWifiGlobals; private final WifiCarrierInfoManager mWifiCarrierInfoManager; private @WifiManager.VerboseLoggingLevel int mVerboseLoggingLevel = WifiManager.VERBOSE_LOGGING_LEVEL_DISABLED; private final RemoteCallbackList mRegisteredWifiLoggingStatusListeners = new RemoteCallbackList<>(); private final FrameworkFacade mFrameworkFacade; private final WifiPermissionsUtil mWifiPermissionsUtil; private final TetheredSoftApTracker mTetheredSoftApTracker; private final LohsSoftApTracker mLohsSoftApTracker; private final BuildProperties mBuildProperties; private final DefaultClientModeManager mDefaultClientModeManager; /** * Callback for use with LocalOnlyHotspot to unregister requesting applications upon death. */ public final class LocalOnlyRequestorCallback implements LocalOnlyHotspotRequestInfo.RequestingApplicationDeathCallback { /** * Called with requesting app has died. */ @Override public void onLocalOnlyHotspotRequestorDeath(LocalOnlyHotspotRequestInfo requestor) { mLog.trace("onLocalOnlyHotspotRequestorDeath pid=%") .c(requestor.getPid()).flush(); mLohsSoftApTracker.stopByRequest(requestor); } } /** * Listen for phone call state events to get active data subcription id. */ private class WifiPhoneStateListener extends PhoneStateListener { WifiPhoneStateListener(Looper looper) { super(new HandlerExecutor(new Handler(looper))); } @Override public void onActiveDataSubscriptionIdChanged(int subId) { // post operation to handler thread mWifiThreadRunner.post(() -> { Log.d(TAG, "OBSERVED active data subscription change, subId: " + subId); mTetheredSoftApTracker.updateSoftApCapabilityWhenCarrierConfigChanged(subId); mActiveModeWarden.updateSoftApCapability( mTetheredSoftApTracker.getSoftApCapability()); }); } } private final WifiLockManager mWifiLockManager; private final WifiMulticastLockManager mWifiMulticastLockManager; private final DppManager mDppManager; private final WifiApConfigStore mWifiApConfigStore; private final WifiThreadRunner mWifiThreadRunner; private final MemoryStoreImpl mMemoryStoreImpl; private final WifiScoreCard mWifiScoreCard; private final WifiHealthMonitor mWifiHealthMonitor; private final WifiDataStall mWifiDataStall; private final WifiNative mWifiNative; private final SimRequiredNotifier mSimRequiredNotifier; private final MakeBeforeBreakManager mMakeBeforeBreakManager; private final LastCallerInfoManager mLastCallerInfoManager; /** * The wrapper of SoftApCallback is used in WifiService internally. * see: {@code WifiManager.SoftApCallback} */ public interface SoftApCallbackInternal { /** * see: {@code WifiManager.SoftApCallback#onStateChanged(int, int)} */ default void onStateChanged(@WifiApState int state, @SapStartFailure int failureReason) {} /** * The callback which only is used in service internally and pass to WifiManager. * It will base on the change to send corresponding callback as below: * 1. onInfoChanged(SoftApInfo) * 2. onInfoChanged(List) * 3. onConnectedClientsChanged(SoftApInfo, List) * 4. onConnectedClientsChanged(List) */ default void onConnectedClientsOrInfoChanged(Map infos, Map> clients, boolean isBridged) {} /** * see: {@code WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} */ default void onCapabilityChanged(@NonNull SoftApCapability softApCapability) {} /** * see: {@code WifiManager.SoftApCallback#onBlockedClientConnecting(WifiClient, int)} */ default void onBlockedClientConnecting(@NonNull WifiClient client, @SapClientBlockedReason int blockedReason) {} } public WifiServiceImpl(Context context, WifiInjector wifiInjector) { mContext = context; mWifiInjector = wifiInjector; mClock = wifiInjector.getClock(); mFacade = mWifiInjector.getFrameworkFacade(); mWifiMetrics = mWifiInjector.getWifiMetrics(); mWifiTrafficPoller = mWifiInjector.getWifiTrafficPoller(); mUserManager = mWifiInjector.getUserManager(); mCountryCode = mWifiInjector.getWifiCountryCode(); mActiveModeWarden = mWifiInjector.getActiveModeWarden(); mScanRequestProxy = mWifiInjector.getScanRequestProxy(); mSettingsStore = mWifiInjector.getWifiSettingsStore(); mPowerManager = mContext.getSystemService(PowerManager.class); mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mWifiLockManager = mWifiInjector.getWifiLockManager(); mWifiMulticastLockManager = mWifiInjector.getWifiMulticastLockManager(); mWifiBackupRestore = mWifiInjector.getWifiBackupRestore(); mSoftApBackupRestore = mWifiInjector.getSoftApBackupRestore(); mWifiApConfigStore = mWifiInjector.getWifiApConfigStore(); mWifiPermissionsUtil = mWifiInjector.getWifiPermissionsUtil(); mLog = mWifiInjector.makeLog(TAG); mFrameworkFacade = wifiInjector.getFrameworkFacade(); mTetheredSoftApTracker = new TetheredSoftApTracker(); mActiveModeWarden.registerSoftApCallback(mTetheredSoftApTracker); mLohsSoftApTracker = new LohsSoftApTracker(); mActiveModeWarden.registerLohsCallback(mLohsSoftApTracker); mWifiNetworkSuggestionsManager = mWifiInjector.getWifiNetworkSuggestionsManager(); mDppManager = mWifiInjector.getDppManager(); mWifiThreadRunner = mWifiInjector.getWifiThreadRunner(); mWifiConfigManager = mWifiInjector.getWifiConfigManager(); mPasspointManager = mWifiInjector.getPasspointManager(); mWifiScoreCard = mWifiInjector.getWifiScoreCard(); mWifiHealthMonitor = wifiInjector.getWifiHealthMonitor(); mMemoryStoreImpl = new MemoryStoreImpl(mContext, mWifiInjector, mWifiScoreCard, mWifiHealthMonitor); mWifiConnectivityManager = wifiInjector.getWifiConnectivityManager(); mWifiDataStall = wifiInjector.getWifiDataStall(); mWifiNative = wifiInjector.getWifiNative(); mCoexManager = wifiInjector.getCoexManager(); mConnectHelper = wifiInjector.getConnectHelper(); mWifiGlobals = wifiInjector.getWifiGlobals(); mSimRequiredNotifier = wifiInjector.getSimRequiredNotifier(); mWifiCarrierInfoManager = wifiInjector.getWifiCarrierInfoManager(); mMakeBeforeBreakManager = mWifiInjector.getMakeBeforeBreakManager(); mLastCallerInfoManager = mWifiInjector.getLastCallerInfoManager(); mBuildProperties = mWifiInjector.getBuildProperties(); mDefaultClientModeManager = mWifiInjector.getDefaultClientModeManager(); } /** * Check if we are ready to start wifi. * * First check if we will be restarting system services to decrypt the device. If the device is * not encrypted, check if Wi-Fi needs to be enabled and start if needed * * This function is used only at boot time. */ public void checkAndStartWifi() { mWifiThreadRunner.post(() -> { if (!mWifiConfigManager.loadFromStore()) { Log.e(TAG, "Failed to load from config store"); } mWifiConfigManager.incrementNumRebootsSinceLastUse(); // config store is read, check if verbose logging is enabled. enableVerboseLoggingInternal( mWifiInjector.getSettingsConfigStore().get(WIFI_VERBOSE_LOGGING_ENABLED) ? 1 : 0); // Check if wi-fi needs to be enabled boolean wifiEnabled = mSettingsStore.isWifiToggleEnabled(); Log.i(TAG, "WifiService starting up with Wi-Fi " + (wifiEnabled ? "enabled" : "disabled")); mWifiInjector.getWifiScanAlwaysAvailableSettingsCompatibility().initialize(); mWifiInjector.getWifiNotificationManager().createNotificationChannels(); mContext.registerReceiver( new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int state = intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE, TelephonyManager.SIM_STATE_UNKNOWN); if (TelephonyManager.SIM_STATE_ABSENT == state) { Log.d(TAG, "resetting networks because SIM was removed"); resetCarrierNetworks(RESET_SIM_REASON_SIM_REMOVED); } } }, new IntentFilter(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED)); mContext.registerReceiver( new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int state = intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE, TelephonyManager.SIM_STATE_UNKNOWN); if (TelephonyManager.SIM_STATE_LOADED == state) { Log.d(TAG, "resetting networks because SIM was loaded"); resetCarrierNetworks(RESET_SIM_REASON_SIM_INSERTED); } } }, new IntentFilter(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED)); mContext.registerReceiver( new BroadcastReceiver() { private int mLastSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; @Override public void onReceive(Context context, Intent intent) { final int subId = intent.getIntExtra("subscription", SubscriptionManager.INVALID_SUBSCRIPTION_ID); if (subId != mLastSubId) { Log.d(TAG, "resetting networks as default data SIM is changed"); resetCarrierNetworks(RESET_SIM_REASON_DEFAULT_DATA_SIM_CHANGED); mLastSubId = subId; mWifiThreadRunner.post(() -> { mWifiDataStall.resetPhoneStateListener(); }); } } }, new IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)); mContext.registerReceiver( new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String countryCode = intent.getStringExtra( TelephonyManager.EXTRA_NETWORK_COUNTRY); Log.d(TAG, "Country code changed to :" + countryCode); mCountryCode.setTelephonyCountryCodeAndUpdate(countryCode); }}, new IntentFilter(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)); mContext.registerReceiver( new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "locale changed"); resetNotificationManager(); }}, new IntentFilter(Intent.ACTION_LOCALE_CHANGED)); // Adding optimizations of only receiving broadcasts when wifi is enabled // can result in race conditions when apps toggle wifi in the background // without active user involvement. Always receive broadcasts. registerForBroadcasts(); mInIdleMode = mPowerManager.isDeviceIdleMode(); mActiveModeWarden.start(); registerForCarrierConfigChange(); mWifiInjector.getAdaptiveConnectivityEnabledSettingObserver().initialize(); }); } private void resetCarrierNetworks(@ClientModeImpl.ResetSimReason int resetReason) { mWifiThreadRunner.post(() -> { Log.d(TAG, "resetting carrier networks since SIM was changed"); if (resetReason == RESET_SIM_REASON_SIM_INSERTED || resetReason == RESET_SIM_REASON_DEFAULT_DATA_SIM_CHANGED) { // clear all SIM related notifications since some action was taken to address // "missing" SIM issue mSimRequiredNotifier.dismissSimRequiredNotification(); } if (resetReason != RESET_SIM_REASON_SIM_INSERTED) { mWifiConfigManager.resetSimNetworks(); mWifiNetworkSuggestionsManager.resetSimNetworkSuggestions(); mPasspointManager.resetSimPasspointNetwork(); mWifiConfigManager.stopRestrictingAutoJoinToSubscriptionId(); } // do additional handling if we are current connected to a sim auth network for (ClientModeManager cmm : mActiveModeWarden.getClientModeManagers()) { cmm.resetSimAuthNetworks(resetReason); } mWifiNetworkSuggestionsManager.resetCarrierPrivilegedApps(); if (resetReason == RESET_SIM_REASON_SIM_INSERTED) { // clear the blocklists in case any SIM based network were disabled due to the SIM // not being available. mWifiConfigManager.enableTemporaryDisabledNetworks(); mWifiConnectivityManager.forceConnectivityScan(ClientModeImpl.WIFI_WORK_SOURCE); } else { // Remove all ephemeral carrier networks keep subscriptionId update with SIM changes mWifiConfigManager.removeEphemeralCarrierNetworks(); } }); } public void handleBootCompleted() { mWifiThreadRunner.post(() -> { Log.d(TAG, "Handle boot completed"); // Register for system broadcasts. IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_USER_REMOVED); intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); intentFilter.addAction(Intent.ACTION_SHUTDOWN); mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(Intent.ACTION_USER_REMOVED)) { UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER); if (userHandle == null) { Log.e(TAG, "User removed broadcast received with no user handle"); return; } mWifiThreadRunner.post(() -> mWifiConfigManager .removeNetworksForUser(userHandle.getIdentifier())); } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, BluetoothAdapter.STATE_DISCONNECTED); boolean isConnected = state != BluetoothAdapter.STATE_DISCONNECTED; mWifiGlobals.setBluetoothConnected(isConnected); for (ClientModeManager cmm : mActiveModeWarden.getClientModeManagers()) { cmm.onBluetoothConnectionStateChanged(); } } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF); boolean isEnabled = state != BluetoothAdapter.STATE_OFF; mWifiGlobals.setBluetoothEnabled(isEnabled); for (ClientModeManager cmm : mActiveModeWarden.getClientModeManagers()) { cmm.onBluetoothConnectionStateChanged(); } } else if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) { handleIdleModeChanged(); } else if (action.equals(Intent.ACTION_SHUTDOWN)) { handleShutDown(); } } }, intentFilter); mMemoryStoreImpl.start(); mPasspointManager.initializeProvisioner( mWifiInjector.getPasspointProvisionerHandlerThread().getLooper()); mWifiInjector.getWifiNetworkFactory().register(); mWifiInjector.getUntrustedWifiNetworkFactory().register(); mWifiInjector.getOemWifiNetworkFactory().register(); mWifiInjector.getWifiP2pConnection().handleBootCompleted(); // Start to listen country code change. mCountryCode.registerListener(new CountryCodeListenerProxy()); mTetheredSoftApTracker.handleBootCompleted(); mWifiInjector.getSarManager().handleBootCompleted(); }); } public void handleUserSwitch(int userId) { Log.d(TAG, "Handle user switch " + userId); mWifiThreadRunner.post(() -> { mWifiConfigManager.handleUserSwitch(userId); resetNotificationManager(); }); } public void handleUserUnlock(int userId) { Log.d(TAG, "Handle user unlock " + userId); mWifiThreadRunner.post(() -> mWifiConfigManager.handleUserUnlock(userId)); } public void handleUserStop(int userId) { Log.d(TAG, "Handle user stop " + userId); mWifiThreadRunner.post(() -> mWifiConfigManager.handleUserStop(userId)); } /** * See {@link android.net.wifi.WifiManager#startScan} * * @param packageName Package name of the app that requests wifi scan. * @param featureId The feature in the package */ @Override public boolean startScan(String packageName, String featureId) { if (enforceChangePermission(packageName) != MODE_ALLOWED) { return false; } int callingUid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); mLog.info("startScan uid=%").c(callingUid).flush(); synchronized (this) { if (mInIdleMode) { // Need to send an immediate scan result broadcast in case the // caller is waiting for a result .. // TODO: investigate if the logic to cancel scans when idle can move to // WifiScanningServiceImpl. This will 1 - clean up WifiServiceImpl and 2 - // avoid plumbing an awkward path to report a cancelled/failed scan. This will // be sent directly until b/31398592 is fixed. sendFailedScanBroadcast(); mScanPending = true; return false; } } try { mWifiPermissionsUtil.enforceCanAccessScanResults(packageName, featureId, callingUid, null); Boolean scanSuccess = mWifiThreadRunner.call(() -> mScanRequestProxy.startScan(callingUid, packageName), null); if (scanSuccess == null) { sendFailedScanBroadcast(); return false; } if (!scanSuccess) { Log.e(TAG, "Failed to start scan"); return false; } } catch (SecurityException e) { Log.w(TAG, "Permission violation - startScan not allowed for" + " uid=" + callingUid + ", packageName=" + packageName + ", reason=" + e); return false; } finally { Binder.restoreCallingIdentity(ident); } return true; } // Send a failed scan broadcast to indicate the current scan request failed. private void sendFailedScanBroadcast() { // clear calling identity to send broadcast long callingIdentity = Binder.clearCallingIdentity(); try { Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, false); mContext.sendBroadcastAsUser(intent, UserHandle.ALL); } finally { // restore calling identity Binder.restoreCallingIdentity(callingIdentity); } } /** * WPS support in Client mode is deprecated. Return null. */ @Override public String getCurrentNetworkWpsNfcConfigurationToken() { // while CLs are in flight, return null here, will be removed (b/72423090) enforceNetworkStackPermission(); if (isVerboseLoggingEnabled()) { mLog.info("getCurrentNetworkWpsNfcConfigurationToken uid=%") .c(Binder.getCallingUid()).flush(); } return null; } private boolean mInIdleMode; private boolean mScanPending; private void handleIdleModeChanged() { boolean doScan = false; synchronized (this) { boolean idle = mPowerManager.isDeviceIdleMode(); if (mInIdleMode != idle) { mInIdleMode = idle; if (!idle) { if (mScanPending) { mScanPending = false; doScan = true; } } } } if (doScan) { // Someone requested a scan while we were idle; do a full scan now. // A security check of the caller's identity was made when the request arrived via // Binder. Now we'll pass the current process's identity to startScan(). startScan(mContext.getOpPackageName(), mContext.getAttributionTag()); } } private void handleShutDown() { // Direct call to notify ActiveModeWarden as soon as possible with the assumption that // notifyShuttingDown() doesn't have codes that may cause concurrentModificationException, // e.g., access to a collection. mActiveModeWarden.notifyShuttingDown(); mWifiThreadRunner.post(()-> { // There is no explicit disconnection event in clientModeImpl during shutdown. // Call resetConnectionState() so that connection duration is calculated // before memory store write triggered by mMemoryStoreImpl.stop(). mWifiScoreCard.resetAllConnectionStates(); mMemoryStoreImpl.stop(); }); } private boolean checkNetworkSettingsPermission(int pid, int uid) { return mContext.checkPermission(android.Manifest.permission.NETWORK_SETTINGS, pid, uid) == PERMISSION_GRANTED; } private boolean checkNetworkSetupWizardPermission(int pid, int uid) { return mContext.checkPermission(android.Manifest.permission.NETWORK_SETUP_WIZARD, pid, uid) == PackageManager.PERMISSION_GRANTED; } private boolean checkMainlineNetworkStackPermission(int pid, int uid) { return mContext.checkPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid) == PackageManager.PERMISSION_GRANTED; } private boolean checkNetworkStackPermission(int pid, int uid) { return mContext.checkPermission(android.Manifest.permission.NETWORK_STACK, pid, uid) == PackageManager.PERMISSION_GRANTED; } private boolean checkNetworkManagedProvisioningPermission(int pid, int uid) { return mContext.checkPermission(android.Manifest.permission.NETWORK_MANAGED_PROVISIONING, pid, uid) == PackageManager.PERMISSION_GRANTED; } /** * Helper method to check if the entity initiating the binder call has any of the signature only * permissions. */ private boolean isPrivileged(int pid, int uid) { return checkNetworkSettingsPermission(pid, uid) || checkNetworkSetupWizardPermission(pid, uid) || checkNetworkStackPermission(pid, uid) || checkNetworkManagedProvisioningPermission(pid, uid) || isSignedWithPlatformKey(uid); } /** Whether the uid is signed with the same key as the platform. */ private boolean isSignedWithPlatformKey(int uid) { return mContext.getPackageManager().checkSignatures(uid, Process.SYSTEM_UID) == PackageManager.SIGNATURE_MATCH; } /** * Helper method to check if the entity initiating the binder call has setup wizard or settings * permissions. */ private boolean isSettingsOrSuw(int pid, int uid) { return checkNetworkSettingsPermission(pid, uid) || checkNetworkSetupWizardPermission(pid, uid); } /** Helper method to check if the entity initiating the binder call is a DO/PO app. */ private boolean isDeviceOrProfileOwner(int uid, String packageName) { return mWifiPermissionsUtil.isDeviceOwner(uid, packageName) || mWifiPermissionsUtil.isProfileOwner(uid, packageName); } private void enforceNetworkSettingsPermission() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.NETWORK_SETTINGS, "WifiService"); } private boolean checkAnyPermissionOf(String... permissions) { for (String permission : permissions) { if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { return true; } } return false; } private void enforceAnyPermissionOf(String... permissions) { if (!checkAnyPermissionOf(permissions)) { throw new SecurityException("Requires one of the following permissions: " + String.join(", ", permissions) + "."); } } private void enforceNetworkStackPermission() { // TODO(b/142554155): Only check for MAINLINE_NETWORK_STACK permission boolean granted = mContext.checkCallingOrSelfPermission( android.Manifest.permission.NETWORK_STACK) == PackageManager.PERMISSION_GRANTED; if (granted) { return; } mContext.enforceCallingOrSelfPermission( NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, "WifiService"); } private void enforceAccessPermission() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, "WifiService"); } private void enforceRestartWifiSubsystemPermission() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RESTART_WIFI_SUBSYSTEM, "WifiService"); } /** * Checks whether the caller can change the wifi state. * Possible results: * 1. Operation is allowed. No exception thrown, and AppOpsManager.MODE_ALLOWED returned. * 2. Operation is not allowed, and caller must be told about this. SecurityException is thrown. * 3. Operation is not allowed, and caller must not be told about this (i.e. must silently * ignore the operation). No exception is thrown, and AppOpsManager.MODE_IGNORED returned. */ @CheckResult private int enforceChangePermission(String callingPackage) { mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); if (checkNetworkSettingsPermission(Binder.getCallingPid(), Binder.getCallingUid())) { return MODE_ALLOWED; } mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, "WifiService"); return mAppOps.noteOp( AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Binder.getCallingUid(), callingPackage); } private void enforceReadCredentialPermission() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_WIFI_CREDENTIAL, "WifiService"); } private void enforceMulticastChangePermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, "WifiService"); } private void enforceConnectivityInternalPermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CONNECTIVITY_INTERNAL, "ConnectivityService"); } private void enforceLocationPermission(String pkgName, @Nullable String featureId, int uid) { mWifiPermissionsUtil.enforceLocationPermission(pkgName, featureId, uid); } /** * Helper method to check if the app is allowed to access public API's deprecated in * {@link Build.VERSION_CODES#Q}. * Note: Invoke mAppOps.checkPackage(uid, packageName) before to ensure correct package name. */ private boolean isTargetSdkLessThanQOrPrivileged(String packageName, int pid, int uid) { return mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.Q, uid) || isPrivileged(pid, uid) || isDeviceOrProfileOwner(uid, packageName) || mWifiPermissionsUtil.isSystem(packageName, uid) // TODO(b/140540984): Remove this bypass. || mWifiPermissionsUtil.checkSystemAlertWindowPermission(uid, packageName); } /** * Helper method to check if the app is allowed to access public API's deprecated in * {@link Build.VERSION_CODES#R}. * Note: Invoke mAppOps.checkPackage(uid, packageName) before to ensure correct package name. */ private boolean isTargetSdkLessThanROrPrivileged(String packageName, int pid, int uid) { return mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.R, uid) || isPrivileged(pid, uid) || isDeviceOrProfileOwner(uid, packageName) || mWifiPermissionsUtil.isSystem(packageName, uid); } /** * Get the current primary ClientModeManager in a thread safe manner, but blocks on the main * Wifi thread. */ private ClientModeManager getPrimaryClientModeManagerBlockingThreadSafe() { return mWifiThreadRunner.call( () -> mActiveModeWarden.getPrimaryClientModeManager(), mDefaultClientModeManager); } /** * see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)} * @param enable {@code true} to enable, {@code false} to disable. * @return {@code true} if the enable/disable operation was * started or is already in the queue. */ @Override public synchronized boolean setWifiEnabled(String packageName, boolean enable) { if (enforceChangePermission(packageName) != MODE_ALLOWED) { return false; } boolean isPrivileged = isPrivileged(Binder.getCallingPid(), Binder.getCallingUid()); if (!isPrivileged && !isDeviceOrProfileOwner(Binder.getCallingUid(), packageName) && !mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.Q, Binder.getCallingUid()) && !mWifiPermissionsUtil.isSystem(packageName, Binder.getCallingUid())) { mLog.info("setWifiEnabled not allowed for uid=%") .c(Binder.getCallingUid()).flush(); return false; } // If Airplane mode is enabled, only privileged apps are allowed to toggle Wifi if (mSettingsStore.isAirplaneModeOn() && !isPrivileged) { mLog.err("setWifiEnabled in Airplane mode: only Settings can toggle wifi").flush(); return false; } // If SoftAp is enabled, only privileged apps are allowed to toggle wifi if (!isPrivileged && mTetheredSoftApTracker.getState() == WIFI_AP_STATE_ENABLED) { mLog.err("setWifiEnabled with SoftAp enabled: only Settings can toggle wifi").flush(); return false; } mLog.info("setWifiEnabled package=% uid=% enable=%").c(packageName) .c(Binder.getCallingUid()).c(enable).flush(); long ident = Binder.clearCallingIdentity(); try { if (!mSettingsStore.handleWifiToggled(enable)) { // Nothing to do if wifi cannot be toggled return true; } } finally { Binder.restoreCallingIdentity(ident); } if (mWifiPermissionsUtil.checkNetworkSettingsPermission(Binder.getCallingUid())) { if (enable) { mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_TOGGLE_WIFI_ON); } else { WifiInfo wifiInfo = getPrimaryClientModeManagerBlockingThreadSafe().syncRequestConnectionInfo(); mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_TOGGLE_WIFI_OFF, wifiInfo == null ? -1 : wifiInfo.getNetworkId()); } } mWifiMetrics.incrementNumWifiToggles(isPrivileged, enable); mActiveModeWarden.wifiToggled(new WorkSource(Binder.getCallingUid(), packageName)); mLastCallerInfoManager.put(LastCallerInfoManager.WIFI_ENABLED, Process.myTid(), Binder.getCallingUid(), Binder.getCallingPid(), packageName, enable); return true; } @RequiresApi(Build.VERSION_CODES.S) @Override public void registerSubsystemRestartCallback(ISubsystemRestartCallback callback) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } enforceAccessPermission(); if (isVerboseLoggingEnabled()) { mLog.info("registerSubsystemRestartCallback uid=%").c(Binder.getCallingUid()).flush(); } mWifiThreadRunner.post(() -> { if (!mActiveModeWarden.registerSubsystemRestartCallback(callback)) { Log.e(TAG, "registerSubsystemRestartCallback: Failed to register callback"); } }); } @RequiresApi(Build.VERSION_CODES.S) @Override public void unregisterSubsystemRestartCallback(ISubsystemRestartCallback callback) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } enforceAccessPermission(); if (isVerboseLoggingEnabled()) { mLog.info("registerSubsystemRestartCallback uid=%").c(Binder.getCallingUid()).flush(); } mWifiThreadRunner.post(() -> { if (!mActiveModeWarden.unregisterSubsystemRestartCallback(callback)) { Log.e(TAG, "unregisterSubsystemRestartCallback: Failed to register callback"); } }); } @RequiresApi(Build.VERSION_CODES.S) @Override public void restartWifiSubsystem() { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } enforceRestartWifiSubsystemPermission(); if (isVerboseLoggingEnabled()) { mLog.info("restartWifiSubsystem uid=%").c(Binder.getCallingUid()).flush(); } mWifiThreadRunner.post(() -> { WifiInfo wifiInfo = mActiveModeWarden.getPrimaryClientModeManager().syncRequestConnectionInfo(); mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_RESTART_WIFI_SUB_SYSTEM, wifiInfo == null ? -1 : wifiInfo.getNetworkId()); mWifiInjector.getSelfRecovery().trigger(REASON_API_CALL); }); } /** * see {@link WifiManager#getWifiState()} * @return One of {@link WifiManager#WIFI_STATE_DISABLED}, * {@link WifiManager#WIFI_STATE_DISABLING}, * {@link WifiManager#WIFI_STATE_ENABLED}, * {@link WifiManager#WIFI_STATE_ENABLING}, * {@link WifiManager#WIFI_STATE_UNKNOWN} */ @Override public int getWifiEnabledState() { enforceAccessPermission(); if (isVerboseLoggingEnabled()) { mLog.info("getWifiEnabledState uid=%").c(Binder.getCallingUid()).flush(); } return getPrimaryClientModeManagerBlockingThreadSafe().syncGetWifiState(); } /** * see {@link WifiManager#getWifiApState()} * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED}, * {@link WifiManager#WIFI_AP_STATE_DISABLING}, * {@link WifiManager#WIFI_AP_STATE_ENABLED}, * {@link WifiManager#WIFI_AP_STATE_ENABLING}, * {@link WifiManager#WIFI_AP_STATE_FAILED} */ @Override public int getWifiApEnabledState() { enforceAccessPermission(); if (isVerboseLoggingEnabled()) { mLog.info("getWifiApEnabledState uid=%").c(Binder.getCallingUid()).flush(); } return mTetheredSoftApTracker.getState(); } /** * see {@link android.net.wifi.WifiManager#updateInterfaceIpState(String, int)} * * The possible modes include: {@link WifiManager#IFACE_IP_MODE_TETHERED}, * {@link WifiManager#IFACE_IP_MODE_LOCAL_ONLY}, * {@link WifiManager#IFACE_IP_MODE_CONFIGURATION_ERROR} * * @param ifaceName String name of the updated interface * @param mode new operating mode of the interface * * @throws SecurityException if the caller does not have permission to call update */ @Override public void updateInterfaceIpState(String ifaceName, int mode) { // NETWORK_STACK is a signature only permission. enforceNetworkStackPermission(); mLog.info("updateInterfaceIpState uid=%").c(Binder.getCallingUid()).flush(); // hand off the work to our handler thread mWifiThreadRunner.post(() -> mLohsSoftApTracker.updateInterfaceIpState(ifaceName, mode)); } /** * see {@link WifiManager#isDefaultCoexAlgorithmEnabled()} * @return {@code true} if the default coex algorithm is enabled. {@code false} otherwise. */ @Override public boolean isDefaultCoexAlgorithmEnabled() { return mContext.getResources().getBoolean(R.bool.config_wifiDefaultCoexAlgorithmEnabled); } /** * see {@link android.net.wifi.WifiManager#setCoexUnsafeChannels(List, int)} * @param unsafeChannels List of {@link CoexUnsafeChannel} to avoid. * @param restrictions Bitmap of {@link CoexRestriction} specifying the mandatory * uses of the specified channels. */ @Override @RequiresApi(Build.VERSION_CODES.S) public void setCoexUnsafeChannels( @NonNull List unsafeChannels, int restrictions) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } mContext.enforceCallingOrSelfPermission( Manifest.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS, "WifiService"); if (unsafeChannels == null) { throw new IllegalArgumentException("unsafeChannels cannot be null"); } if (mContext.getResources().getBoolean(R.bool.config_wifiDefaultCoexAlgorithmEnabled)) { Log.e(TAG, "setCoexUnsafeChannels called but default coex algorithm is enabled"); return; } mWifiThreadRunner.post(() -> mCoexManager.setCoexUnsafeChannels(unsafeChannels, restrictions)); } /** * See {@link WifiManager#registerCoexCallback(WifiManager.CoexCallback)} */ @RequiresApi(Build.VERSION_CODES.S) public void registerCoexCallback(@NonNull ICoexCallback callback) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } mContext.enforceCallingOrSelfPermission( Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS, "WifiService"); if (callback == null) { throw new IllegalArgumentException("callback must not be null"); } if (isVerboseLoggingEnabled()) { mLog.info("registerCoexCallback uid=%").c(Binder.getCallingUid()).flush(); } mWifiThreadRunner.post(() -> mCoexManager.registerRemoteCoexCallback(callback)); } /** * See {@link WifiManager#unregisterCoexCallback(WifiManager.CoexCallback)} */ @RequiresApi(Build.VERSION_CODES.S) public void unregisterCoexCallback(@NonNull ICoexCallback callback) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } mContext.enforceCallingOrSelfPermission( Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS, "WifiService"); if (callback == null) { throw new IllegalArgumentException("callback must not be null"); } if (isVerboseLoggingEnabled()) { mLog.info("unregisterCoexCallback uid=%").c(Binder.getCallingUid()).flush(); } mWifiThreadRunner.post(() -> mCoexManager.unregisterRemoteCoexCallback(callback)); } /** * see {@link android.net.wifi.WifiManager#startSoftAp(WifiConfiguration)} * @param wifiConfig SSID, security and channel details as part of WifiConfiguration * @return {@code true} if softap start was triggered * @throws SecurityException if the caller does not have permission to start softap */ @Override public boolean startSoftAp(WifiConfiguration wifiConfig, String packageName) { // NETWORK_STACK is a signature only permission. enforceNetworkStackPermission(); mLog.info("startSoftAp uid=%").c(Binder.getCallingUid()).flush(); SoftApConfiguration softApConfig = null; if (wifiConfig != null) { softApConfig = ApConfigUtil.fromWifiConfiguration(wifiConfig); if (softApConfig == null) { return false; } } if (!mTetheredSoftApTracker.setEnablingIfAllowed()) { mLog.err("Tethering is already active.").flush(); return false; } WorkSource requestorWs = new WorkSource(Binder.getCallingUid(), packageName); if (!mWifiThreadRunner.call( () -> mActiveModeWarden.canRequestMoreSoftApManagers(requestorWs), false)) { // Take down LOHS if it is up. mLohsSoftApTracker.stopAll(); } if (!startSoftApInternal(new SoftApModeConfiguration( WifiManager.IFACE_IP_MODE_TETHERED, softApConfig, mTetheredSoftApTracker.getSoftApCapability()), requestorWs)) { mTetheredSoftApTracker.setFailedWhileEnabling(); return false; } mLastCallerInfoManager.put(LastCallerInfoManager.SOFT_AP, Process.myTid(), Binder.getCallingUid(), Binder.getCallingPid(), packageName, true); return true; } /** * see {@link android.net.wifi.WifiManager#startTetheredHotspot(SoftApConfiguration)} * @param softApConfig SSID, security and channel details as part of SoftApConfiguration * @return {@code true} if softap start was triggered * @throws SecurityException if the caller does not have permission to start softap */ @Override public boolean startTetheredHotspot(@Nullable SoftApConfiguration softApConfig, @NonNull String packageName) { // NETWORK_STACK is a signature only permission. enforceNetworkStackPermission(); mLog.info("startTetheredHotspot uid=%").c(Binder.getCallingUid()).flush(); if (!mTetheredSoftApTracker.setEnablingIfAllowed()) { mLog.err("Tethering is already active.").flush(); return false; } WorkSource requestorWs = new WorkSource(Binder.getCallingUid(), packageName); if (!mWifiThreadRunner.call( () -> mActiveModeWarden.canRequestMoreSoftApManagers(requestorWs), false)) { // Take down LOHS if it is up. mLohsSoftApTracker.stopAll(); } if (!startSoftApInternal(new SoftApModeConfiguration( WifiManager.IFACE_IP_MODE_TETHERED, softApConfig, mTetheredSoftApTracker.getSoftApCapability()), requestorWs)) { mTetheredSoftApTracker.setFailedWhileEnabling(); return false; } mLastCallerInfoManager.put(LastCallerInfoManager.TETHERED_HOTSPOT, Process.myTid(), Binder.getCallingUid(), Binder.getCallingPid(), packageName, true); return true; } /** * Internal method to start softap mode. Callers of this method should have already checked * proper permissions beyond the NetworkStack permission. */ private boolean startSoftApInternal(SoftApModeConfiguration apConfig, WorkSource requestorWs) { int uid = Binder.getCallingUid(); boolean privileged = isSettingsOrSuw(Binder.getCallingPid(), uid); mLog.trace("startSoftApInternal uid=% mode=%") .c(uid).c(apConfig.getTargetMode()).flush(); // null wifiConfig is a meaningful input for CMD_SET_AP; it means to use the persistent // AP config. SoftApConfiguration softApConfig = apConfig.getSoftApConfiguration(); if (softApConfig != null && (!WifiApConfigStore.validateApWifiConfiguration( softApConfig, privileged, mContext))) { Log.e(TAG, "Invalid SoftApConfiguration"); return false; } mActiveModeWarden.startSoftAp(apConfig, requestorWs); return true; } /** * see {@link android.net.wifi.WifiManager#stopSoftAp()} * @return {@code true} if softap stop was triggered * @throws SecurityException if the caller does not have permission to stop softap */ @Override public boolean stopSoftAp() { // NETWORK_STACK is a signature only permission. enforceNetworkStackPermission(); // only permitted callers are allowed to this point - they must have gone through // connectivity service since this method is protected with the NETWORK_STACK PERMISSION mLog.info("stopSoftAp uid=%").c(Binder.getCallingUid()).flush(); stopSoftApInternal(WifiManager.IFACE_IP_MODE_TETHERED); mLastCallerInfoManager.put(LastCallerInfoManager.SOFT_AP, Process.myTid(), Binder.getCallingUid(), Binder.getCallingPid(), "", false); return true; } /** * Internal method to stop softap mode. * * Callers of this method should have already checked * proper permissions beyond the NetworkStack permission. * * @param mode the operating mode of APs to bring down (ex, * {@link WifiManager.IFACE_IP_MODE_TETHERED} or * {@link WifiManager.IFACE_IP_MODE_LOCAL_ONLY}). * Use {@link WifiManager.IFACE_IP_MODE_UNSPECIFIED} to stop all APs. */ private void stopSoftApInternal(int mode) { mLog.trace("stopSoftApInternal uid=% mode=%").c(Binder.getCallingUid()).c(mode).flush(); mActiveModeWarden.stopSoftAp(mode); } private final class CountryCodeListenerProxy implements WifiCountryCode.ChangeListener { @Override public void onDriverCountryCodeChanged(String countryCode) { // post operation to handler thread mWifiThreadRunner.post(() -> { Log.i(TAG, "onDriverCountryCodeChanged " + countryCode); mTetheredSoftApTracker.updateAvailChannelListInSoftApCapability(); mActiveModeWarden.updateSoftApCapability( mTetheredSoftApTracker.getSoftApCapability()); }); } } /** * SoftAp callback */ private final class TetheredSoftApTracker implements SoftApCallbackInternal { /** * State of tethered SoftAP * One of: {@link WifiManager#WIFI_AP_STATE_DISABLED}, * {@link WifiManager#WIFI_AP_STATE_DISABLING}, * {@link WifiManager#WIFI_AP_STATE_ENABLED}, * {@link WifiManager#WIFI_AP_STATE_ENABLING}, * {@link WifiManager#WIFI_AP_STATE_FAILED} */ private final Object mLock = new Object(); private int mTetheredSoftApState = WIFI_AP_STATE_DISABLED; private Map> mTetheredSoftApConnectedClientsMap = new HashMap(); private Map mTetheredSoftApInfoMap = new HashMap(); private boolean mIsBridgedMode = false; // TODO: We need to maintain two capability. One for LTE + SAP and one for WIFI + SAP private SoftApCapability mTetheredSoftApCapability = null; private boolean mIsBootComplete = false; public void handleBootCompleted() { mIsBootComplete = true; updateAvailChannelListInSoftApCapability(); } public int getState() { synchronized (mLock) { return mTetheredSoftApState; } } public boolean setEnablingIfAllowed() { synchronized (mLock) { if (mTetheredSoftApState != WIFI_AP_STATE_DISABLED && mTetheredSoftApState != WIFI_AP_STATE_FAILED) { return false; } mTetheredSoftApState = WIFI_AP_STATE_ENABLING; return true; } } public void setFailedWhileEnabling() { synchronized (mLock) { if (mTetheredSoftApState == WIFI_AP_STATE_ENABLING) { mTetheredSoftApState = WIFI_AP_STATE_FAILED; } } } public Map> getConnectedClients() { synchronized (mLock) { return mTetheredSoftApConnectedClientsMap; } } public Map getSoftApInfos() { synchronized (mLock) { return mTetheredSoftApInfoMap; } } public boolean getIsBridgedMode() { synchronized (mLock) { return mIsBridgedMode; } } public SoftApCapability getSoftApCapability() { synchronized (mLock) { if (mTetheredSoftApCapability == null) { mTetheredSoftApCapability = ApConfigUtil.updateCapabilityFromResource(mContext); // Default country code mTetheredSoftApCapability = updateSoftApCapabilityWithAvailableChannelList( mTetheredSoftApCapability); } return mTetheredSoftApCapability; } } private SoftApCapability updateSoftApCapabilityWithAvailableChannelList( @NonNull SoftApCapability softApCapability) { SoftApCapability newSoftApCapability = new SoftApCapability(softApCapability); if (!mIsBootComplete) { // The available channel list is from wificond. // It might be a failure or stuck during wificond init. return newSoftApCapability; } List supportedChannelList = null; if (ApConfigUtil.isSoftAp24GhzSupported(mContext)) { supportedChannelList = ApConfigUtil.getAvailableChannelFreqsForBand( SoftApConfiguration.BAND_2GHZ, mWifiNative, mContext.getResources(), false); if (supportedChannelList != null) { newSoftApCapability.setSupportedChannelList( SoftApConfiguration.BAND_2GHZ, supportedChannelList.stream().mapToInt(Integer::intValue).toArray()); } } if (ApConfigUtil.isSoftAp5GhzSupported(mContext)) { supportedChannelList = ApConfigUtil.getAvailableChannelFreqsForBand( SoftApConfiguration.BAND_5GHZ, mWifiNative, mContext.getResources(), false); if (supportedChannelList != null) { newSoftApCapability.setSupportedChannelList( SoftApConfiguration.BAND_5GHZ, supportedChannelList.stream().mapToInt(Integer::intValue).toArray()); } } if (ApConfigUtil.isSoftAp6GhzSupported(mContext)) { supportedChannelList = ApConfigUtil.getAvailableChannelFreqsForBand( SoftApConfiguration.BAND_6GHZ, mWifiNative, mContext.getResources(), false); if (supportedChannelList != null) { newSoftApCapability.setSupportedChannelList( SoftApConfiguration.BAND_6GHZ, supportedChannelList.stream().mapToInt(Integer::intValue).toArray()); } } if (ApConfigUtil.isSoftAp60GhzSupported(mContext)) { supportedChannelList = ApConfigUtil.getAvailableChannelFreqsForBand( SoftApConfiguration.BAND_60GHZ, mWifiNative, mContext.getResources(), false); if (supportedChannelList != null) { newSoftApCapability.setSupportedChannelList( SoftApConfiguration.BAND_60GHZ, supportedChannelList.stream().mapToInt(Integer::intValue).toArray()); } } return newSoftApCapability; } public void updateAvailChannelListInSoftApCapability() { onCapabilityChanged(updateSoftApCapabilityWithAvailableChannelList( getSoftApCapability())); } public void updateSoftApCapabilityWhenCarrierConfigChanged(int subId) { CarrierConfigManager carrierConfigManager = mContext.getSystemService(CarrierConfigManager.class); if (carrierConfigManager == null) return; PersistableBundle carrierConfig = carrierConfigManager.getConfigForSubId(subId); if (carrierConfig == null) return; int carrierMaxClient = carrierConfig.getInt( CarrierConfigManager.Wifi.KEY_HOTSPOT_MAX_CLIENT_COUNT); int finalSupportedClientNumber = mContext.getResources().getInteger( R.integer.config_wifiHardwareSoftapMaxClientCount); if (carrierMaxClient > 0) { finalSupportedClientNumber = Math.min(finalSupportedClientNumber, carrierMaxClient); } if (finalSupportedClientNumber == getSoftApCapability().getMaxSupportedClients()) { return; } SoftApCapability newSoftApCapability = new SoftApCapability(mTetheredSoftApCapability); newSoftApCapability.setMaxSupportedClients( finalSupportedClientNumber); onCapabilityChanged(newSoftApCapability); } private final RemoteCallbackList mRegisteredSoftApCallbacks = new RemoteCallbackList<>(); public boolean registerSoftApCallback(ISoftApCallback callback) { return mRegisteredSoftApCallbacks.register(callback); } public void unregisterSoftApCallback(ISoftApCallback callback) { mRegisteredSoftApCallbacks.unregister(callback); } /** * Called when soft AP state changes. * * @param state new new AP state. One of {@link #WIFI_AP_STATE_DISABLED}, * {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED}, * {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED} * @param failureReason reason when in failed state. One of * {@link #SAP_START_FAILURE_GENERAL}, {@link #SAP_START_FAILURE_NO_CHANNEL} */ @Override public void onStateChanged(int state, int failureReason) { synchronized (mLock) { mTetheredSoftApState = state; } int itemCount = mRegisteredSoftApCallbacks.beginBroadcast(); for (int i = 0; i < itemCount; i++) { try { mRegisteredSoftApCallbacks.getBroadcastItem(i).onStateChanged(state, failureReason); } catch (RemoteException e) { Log.e(TAG, "onStateChanged: remote exception -- " + e); } } mRegisteredSoftApCallbacks.finishBroadcast(); } /** * Called when the connected clients to soft AP changes. * * @param clients connected clients to soft AP */ @Override public void onConnectedClientsOrInfoChanged(Map infos, Map> clients, boolean isBridged) { synchronized (mLock) { mIsBridgedMode = isBridged; if (infos.size() == 0 && isBridged) { Log.d(TAG, "ShutDown bridged mode, clear isBridged cache in Service"); mIsBridgedMode = false; } mTetheredSoftApConnectedClientsMap = ApConfigUtil.deepCopyForWifiClientListMap(clients); mTetheredSoftApInfoMap = ApConfigUtil.deepCopyForSoftApInfoMap(infos); } int itemCount = mRegisteredSoftApCallbacks.beginBroadcast(); for (int i = 0; i < itemCount; i++) { try { mRegisteredSoftApCallbacks.getBroadcastItem(i).onConnectedClientsOrInfoChanged( ApConfigUtil.deepCopyForSoftApInfoMap(mTetheredSoftApInfoMap), ApConfigUtil.deepCopyForWifiClientListMap( mTetheredSoftApConnectedClientsMap), isBridged, false); } catch (RemoteException e) { Log.e(TAG, "onConnectedClientsOrInfoChanged: remote exception -- " + e); } } mRegisteredSoftApCallbacks.finishBroadcast(); } /** * Called when capability of softap changes. * * @param capability is the softap capability. {@link SoftApCapability} */ @Override public void onCapabilityChanged(SoftApCapability capability) { synchronized (mLock) { if (Objects.equals(capability, mTetheredSoftApCapability)) { return; } mTetheredSoftApCapability = new SoftApCapability(capability); } int itemCount = mRegisteredSoftApCallbacks.beginBroadcast(); for (int i = 0; i < itemCount; i++) { try { mRegisteredSoftApCallbacks.getBroadcastItem(i).onCapabilityChanged( mTetheredSoftApCapability); } catch (RemoteException e) { Log.e(TAG, "onCapabiliyChanged: remote exception -- " + e); } } mRegisteredSoftApCallbacks.finishBroadcast(); } /** * Called when client trying to connect but device blocked the client with specific reason. * * @param client the currently blocked client. * @param blockedReason one of blocked reason from * {@link WifiManager.SapClientBlockedReason} */ @Override public void onBlockedClientConnecting(WifiClient client, int blockedReason) { int itemCount = mRegisteredSoftApCallbacks.beginBroadcast(); for (int i = 0; i < itemCount; i++) { try { mRegisteredSoftApCallbacks.getBroadcastItem(i).onBlockedClientConnecting(client, blockedReason); } catch (RemoteException e) { Log.e(TAG, "onBlockedClientConnecting: remote exception -- " + e); } } mRegisteredSoftApCallbacks.finishBroadcast(); } } /** * Implements LOHS behavior on top of the existing SoftAp API. */ private final class LohsSoftApTracker implements SoftApCallbackInternal { @GuardedBy("mLocalOnlyHotspotRequests") private final HashMap mLocalOnlyHotspotRequests = new HashMap<>(); /** Currently-active config, to be sent to shared clients registering later. */ @GuardedBy("mLocalOnlyHotspotRequests") private SoftApModeConfiguration mActiveConfig = null; /** * Whether we are currently operating in exclusive mode (i.e. whether a custom config is * active). */ @GuardedBy("mLocalOnlyHotspotRequests") private boolean mIsExclusive = false; @GuardedBy("mLocalOnlyHotspotRequests") private String mLohsInterfaceName; /** * State of local-only hotspot * One of: {@link WifiManager#WIFI_AP_STATE_DISABLED}, * {@link WifiManager#WIFI_AP_STATE_DISABLING}, * {@link WifiManager#WIFI_AP_STATE_ENABLED}, * {@link WifiManager#WIFI_AP_STATE_ENABLING}, * {@link WifiManager#WIFI_AP_STATE_FAILED} */ @GuardedBy("mLocalOnlyHotspotRequests") private int mLohsState = WIFI_AP_STATE_DISABLED; @GuardedBy("mLocalOnlyHotspotRequests") private int mLohsInterfaceMode = WifiManager.IFACE_IP_MODE_UNSPECIFIED; private SoftApCapability mLohsSoftApCapability = null; public SoftApCapability getSoftApCapability() { if (mLohsSoftApCapability == null) { mLohsSoftApCapability = ApConfigUtil.updateCapabilityFromResource(mContext); } return mLohsSoftApCapability; } public void updateInterfaceIpState(String ifaceName, int mode) { // update interface IP state related to local-only hotspot synchronized (mLocalOnlyHotspotRequests) { Log.d(TAG, "updateInterfaceIpState: ifaceName=" + ifaceName + " mode=" + mode + " previous LOHS mode= " + mLohsInterfaceMode); switch (mode) { case WifiManager.IFACE_IP_MODE_LOCAL_ONLY: // first make sure we have registered requests. if (mLocalOnlyHotspotRequests.isEmpty()) { // we don't have requests... stop the hotspot Log.wtf(TAG, "Starting LOHS without any requests?"); stopSoftApInternal(WifiManager.IFACE_IP_MODE_LOCAL_ONLY); return; } // LOHS is ready to go! Call our registered requestors! mLohsInterfaceName = ifaceName; mLohsInterfaceMode = mode; sendHotspotStartedMessageToAllLOHSRequestInfoEntriesLocked(); break; case WifiManager.IFACE_IP_MODE_TETHERED: if (mLohsInterfaceName != null && mLohsInterfaceName.equals(ifaceName)) { /* This shouldn't happen except in a race, but if it does, tear down * the LOHS and let tethering win. * * If concurrent SAPs are allowed, the interface names will differ, * so we don't have to check the config here. */ Log.e(TAG, "Unexpected IP mode change on " + ifaceName); mLohsInterfaceName = null; mLohsInterfaceMode = WifiManager.IFACE_IP_MODE_UNSPECIFIED; sendHotspotFailedMessageToAllLOHSRequestInfoEntriesLocked( LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE); } break; case WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR: if (ifaceName == null) { // All softAps mLohsInterfaceName = null; mLohsInterfaceMode = mode; sendHotspotFailedMessageToAllLOHSRequestInfoEntriesLocked( LocalOnlyHotspotCallback.ERROR_GENERIC); stopSoftApInternal(WifiManager.IFACE_IP_MODE_UNSPECIFIED); } else if (ifaceName.equals(mLohsInterfaceName)) { mLohsInterfaceName = null; mLohsInterfaceMode = mode; sendHotspotFailedMessageToAllLOHSRequestInfoEntriesLocked( LocalOnlyHotspotCallback.ERROR_GENERIC); stopSoftApInternal(WifiManager.IFACE_IP_MODE_LOCAL_ONLY); } else { // Not for LOHS. This is the wrong place to do this, but... stopSoftApInternal(WifiManager.IFACE_IP_MODE_TETHERED); } break; case WifiManager.IFACE_IP_MODE_UNSPECIFIED: if (ifaceName == null || ifaceName.equals(mLohsInterfaceName)) { mLohsInterfaceName = null; mLohsInterfaceMode = mode; } break; default: mLog.warn("updateInterfaceIpState: unknown mode %").c(mode).flush(); } } } /** * Helper method to send a HOTSPOT_FAILED message to all registered LocalOnlyHotspotRequest * callers and clear the registrations. * * Callers should already hold the mLocalOnlyHotspotRequests lock. */ @GuardedBy("mLocalOnlyHotspotRequests") private void sendHotspotFailedMessageToAllLOHSRequestInfoEntriesLocked(int reason) { for (LocalOnlyHotspotRequestInfo requestor : mLocalOnlyHotspotRequests.values()) { try { requestor.sendHotspotFailedMessage(reason); requestor.unlinkDeathRecipient(); } catch (RemoteException e) { // This will be cleaned up by binder death handling } } // Since all callers were notified, now clear the registrations. mLocalOnlyHotspotRequests.clear(); } /** * Helper method to send a HOTSPOT_STOPPED message to all registered LocalOnlyHotspotRequest * callers and clear the registrations. * * Callers should already hold the mLocalOnlyHotspotRequests lock. */ @GuardedBy("mLocalOnlyHotspotRequests") private void sendHotspotStoppedMessageToAllLOHSRequestInfoEntriesLocked() { for (LocalOnlyHotspotRequestInfo requestor : mLocalOnlyHotspotRequests.values()) { try { requestor.sendHotspotStoppedMessage(); requestor.unlinkDeathRecipient(); } catch (RemoteException e) { // This will be cleaned up by binder death handling } } // Since all callers were notified, now clear the registrations. mLocalOnlyHotspotRequests.clear(); } /** * Add a new LOHS client */ private int start(int pid, LocalOnlyHotspotRequestInfo request) { synchronized (mLocalOnlyHotspotRequests) { // does this caller already have a request? if (mLocalOnlyHotspotRequests.get(pid) != null) { mLog.trace("caller already has an active request").flush(); throw new IllegalStateException( "Caller already has an active LocalOnlyHotspot request"); } // Never accept exclusive requests (with custom configuration) at the same time as // shared requests. if (!mLocalOnlyHotspotRequests.isEmpty()) { boolean requestIsExclusive = request.getCustomConfig() != null; if (mIsExclusive || requestIsExclusive) { mLog.trace("Cannot share with existing LOHS request due to custom config") .flush(); return LocalOnlyHotspotCallback.ERROR_GENERIC; } } // At this point, the request is accepted. if (mLocalOnlyHotspotRequests.isEmpty()) { mWifiThreadRunner.post(() -> { startForFirstRequestLocked(request); }); } else if (mLohsInterfaceMode == WifiManager.IFACE_IP_MODE_LOCAL_ONLY) { // LOHS has already started up for an earlier request, so we can send the // current config to the incoming request right away. try { mLog.trace("LOHS already up, trigger onStarted callback").flush(); request.sendHotspotStartedMessage(mActiveConfig.getSoftApConfiguration()); } catch (RemoteException e) { return LocalOnlyHotspotCallback.ERROR_GENERIC; } } mLocalOnlyHotspotRequests.put(pid, request); return LocalOnlyHotspotCallback.REQUEST_REGISTERED; } } @GuardedBy("mLocalOnlyHotspotRequests") private void startForFirstRequestLocked(LocalOnlyHotspotRequestInfo request) { int band = WifiApConfigStore.generateDefaultBand(mContext); // For auto only if (hasAutomotiveFeature(mContext)) { if (mContext.getResources().getBoolean(R.bool.config_wifiLocalOnlyHotspot6ghz) && ApConfigUtil.isBandSupported(SoftApConfiguration.BAND_6GHZ, mContext)) { band = SoftApConfiguration.BAND_6GHZ; } else if (mContext.getResources().getBoolean( R.bool.config_wifi_local_only_hotspot_5ghz) && ApConfigUtil.isBandSupported(SoftApConfiguration.BAND_5GHZ, mContext)) { band = SoftApConfiguration.BAND_5GHZ; } } SoftApConfiguration softApConfig = mWifiApConfigStore.generateLocalOnlyHotspotConfig( mContext, band, request.getCustomConfig()); mActiveConfig = new SoftApModeConfiguration( WifiManager.IFACE_IP_MODE_LOCAL_ONLY, softApConfig, mLohsSoftApTracker.getSoftApCapability()); mIsExclusive = (request.getCustomConfig() != null); startSoftApInternal(mActiveConfig, request.getWorkSource()); } /** * Requests that any local-only hotspot be stopped. */ public void stopAll() { synchronized (mLocalOnlyHotspotRequests) { if (!mLocalOnlyHotspotRequests.isEmpty()) { // This is used to take down LOHS when tethering starts, and in that // case we send failed instead of stopped. // TODO check if that is right. Calling onFailed instead of onStopped when the // hotspot is already started does not seem to match the documentation sendHotspotFailedMessageToAllLOHSRequestInfoEntriesLocked( LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE); stopIfEmptyLocked(); } } } /** * Unregisters the LOHS request from the given process and stops LOHS if no other clients. */ public void stopByPid(int pid) { synchronized (mLocalOnlyHotspotRequests) { LocalOnlyHotspotRequestInfo requestInfo = mLocalOnlyHotspotRequests.remove(pid); if (requestInfo == null) return; requestInfo.unlinkDeathRecipient(); stopIfEmptyLocked(); } } /** * Unregisters LocalOnlyHotspot request and stops the hotspot if needed. */ public void stopByRequest(LocalOnlyHotspotRequestInfo request) { synchronized (mLocalOnlyHotspotRequests) { if (mLocalOnlyHotspotRequests.remove(request.getPid()) == null) { mLog.trace("LocalOnlyHotspotRequestInfo not found to remove").flush(); return; } stopIfEmptyLocked(); } } @GuardedBy("mLocalOnlyHotspotRequests") private void stopIfEmptyLocked() { if (mLocalOnlyHotspotRequests.isEmpty()) { mActiveConfig = null; mIsExclusive = false; mLohsInterfaceName = null; mLohsInterfaceMode = WifiManager.IFACE_IP_MODE_UNSPECIFIED; stopSoftApInternal(WifiManager.IFACE_IP_MODE_LOCAL_ONLY); } } /** * Helper method to send a HOTSPOT_STARTED message to all registered LocalOnlyHotspotRequest * callers. * * Callers should already hold the mLocalOnlyHotspotRequests lock. */ @GuardedBy("mLocalOnlyHotspotRequests") private void sendHotspotStartedMessageToAllLOHSRequestInfoEntriesLocked() { for (LocalOnlyHotspotRequestInfo requestor : mLocalOnlyHotspotRequests.values()) { try { requestor.sendHotspotStartedMessage(mActiveConfig.getSoftApConfiguration()); } catch (RemoteException e) { // This will be cleaned up by binder death handling } } } @Override public void onStateChanged(int state, int failureReason) { // The AP state update from ClientModeImpl for softap synchronized (mLocalOnlyHotspotRequests) { Log.d(TAG, "lohs.onStateChanged: currentState=" + state + " previousState=" + mLohsState + " errorCode= " + failureReason + " ifaceName=" + mLohsInterfaceName); // check if we have a failure - since it is possible (worst case scenario where // WifiController and ClientModeImpl are out of sync wrt modes) to get two FAILED // notifications in a row, we need to handle this first. if (state == WIFI_AP_STATE_FAILED) { // update registered LOHS callbacks if we see a failure int errorToReport = ERROR_GENERIC; if (failureReason == SAP_START_FAILURE_NO_CHANNEL) { errorToReport = ERROR_NO_CHANNEL; } // holding the required lock: send message to requestors and clear the list sendHotspotFailedMessageToAllLOHSRequestInfoEntriesLocked(errorToReport); // also need to clear interface ip state updateInterfaceIpState(mLohsInterfaceName, WifiManager.IFACE_IP_MODE_UNSPECIFIED); } else if (state == WIFI_AP_STATE_DISABLING || state == WIFI_AP_STATE_DISABLED) { // softap is shutting down or is down... let requestors know via the // onStopped call // if we are currently in hotspot mode, then trigger onStopped for registered // requestors, otherwise something odd happened and we should clear state if (mLohsInterfaceName != null && mLohsInterfaceMode == WifiManager.IFACE_IP_MODE_LOCAL_ONLY) { // holding the required lock: send message to requestors and clear the list sendHotspotStoppedMessageToAllLOHSRequestInfoEntriesLocked(); } else { // LOHS not active: report an error (still holding the required lock) sendHotspotFailedMessageToAllLOHSRequestInfoEntriesLocked(ERROR_GENERIC); } // also clear interface ip state updateInterfaceIpState(mLohsInterfaceName, WifiManager.IFACE_IP_MODE_UNSPECIFIED); } // For enabling and enabled, just record the new state mLohsState = state; } } } /** * see {@link android.net.wifi.WifiManager#registerSoftApCallback(Executor, * WifiManager.SoftApCallback)} * * @param callback Soft AP callback to register * * @throws SecurityException if the caller does not have permission to register a callback * @throws RemoteException if remote exception happens * @throws IllegalArgumentException if the arguments are null or invalid */ @Override public void registerSoftApCallback(ISoftApCallback callback) { // verify arguments if (callback == null) { throw new IllegalArgumentException("Callback must not be null"); } int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid) && !checkNetworkSettingsPermission(pid, uid) && !checkMainlineNetworkStackPermission(pid, uid)) { // random apps should not be allowed to read the user specified config throw new SecurityException("App not allowed to read WiFi Ap information " + "(uid/pid = " + uid + "/" + pid + ")"); } if (isVerboseLoggingEnabled()) { mLog.info("registerSoftApCallback uid=%").c(Binder.getCallingUid()).flush(); } // post operation to handler thread mWifiThreadRunner.post(() -> { if (!mTetheredSoftApTracker.registerSoftApCallback(callback)) { Log.e(TAG, "registerSoftApCallback: Failed to add callback"); return; } // Update the client about the current state immediately after registering the callback try { callback.onStateChanged(mTetheredSoftApTracker.getState(), 0); callback.onConnectedClientsOrInfoChanged(mTetheredSoftApTracker.getSoftApInfos(), mTetheredSoftApTracker.getConnectedClients(), mTetheredSoftApTracker.getIsBridgedMode(), true); callback.onCapabilityChanged(mTetheredSoftApTracker.getSoftApCapability()); } catch (RemoteException e) { Log.e(TAG, "registerSoftApCallback: remote exception -- " + e); } }); } /** * see {@link android.net.wifi.WifiManager#unregisterSoftApCallback(WifiManager.SoftApCallback)} * * @param callback Soft AP callback to unregister * * @throws SecurityException if the caller does not have permission to register a callback */ @Override public void unregisterSoftApCallback(ISoftApCallback callback) { int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid) && !checkNetworkSettingsPermission(pid, uid) && !checkMainlineNetworkStackPermission(pid, uid)) { // random apps should not be allowed to read the user specified config throw new SecurityException("App not allowed to read WiFi Ap information " + "(uid/pid = " + uid + "/" + pid + ")"); } if (isVerboseLoggingEnabled()) { mLog.info("unregisterSoftApCallback uid=%").c(Binder.getCallingUid()).flush(); } // post operation to handler thread mWifiThreadRunner.post(() -> mTetheredSoftApTracker.unregisterSoftApCallback(callback)); } /** * Temporary method used for testing while start is not fully implemented. This * method allows unit tests to register callbacks directly for testing mechanisms triggered by * softap mode changes. */ @VisibleForTesting void registerLOHSForTest(int pid, LocalOnlyHotspotRequestInfo request) { mLohsSoftApTracker.start(pid, request); } /** * Method to start LocalOnlyHotspot. In this method, permissions, settings and modes are * checked to verify that we can enter softapmode. This method returns * {@link LocalOnlyHotspotCallback#REQUEST_REGISTERED} if we will attempt to start, otherwise, * possible startup erros may include tethering being disallowed failure reason {@link * LocalOnlyHotspotCallback#ERROR_TETHERING_DISALLOWED} or an incompatible mode failure reason * {@link LocalOnlyHotspotCallback#ERROR_INCOMPATIBLE_MODE}. * * see {@link WifiManager#startLocalOnlyHotspot(LocalOnlyHotspotCallback)} * * @param callback Callback to communicate with WifiManager and allow cleanup if the app dies. * @param packageName String name of the calling package. * @param featureId The feature in the package * @param customConfig Custom configuration to be applied to the hotspot, or null for a shared * hotspot with framework-generated config. * * @return int return code for attempt to start LocalOnlyHotspot. * * @throws SecurityException if the caller does not have permission to start a Local Only * Hotspot. * @throws IllegalStateException if the caller attempts to start the LocalOnlyHotspot while they * have an outstanding request. */ @Override public int startLocalOnlyHotspot(ILocalOnlyHotspotCallback callback, String packageName, String featureId, SoftApConfiguration customConfig) { // first check if the caller has permission to start a local only hotspot // need to check for WIFI_STATE_CHANGE and location permission final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); mLog.info("start uid=% pid=%").c(uid).c(pid).flush(); final WorkSource requestorWs; // Permission requirements are different with/without custom config. if (customConfig == null) { if (enforceChangePermission(packageName) != MODE_ALLOWED) { return LocalOnlyHotspotCallback.ERROR_GENERIC; } enforceLocationPermission(packageName, featureId, uid); long ident = Binder.clearCallingIdentity(); try { // also need to verify that Locations services are enabled. if (!mWifiPermissionsUtil.isLocationModeEnabled()) { throw new SecurityException("Location mode is not enabled."); } // TODO(b/162344695): Exception added for LOHS. This exception is need to avoid // breaking existing LOHS behavior: LOHS AP iface is allowed to delete STA iface // (even if LOHS app has lower priority than user toggled on STA iface). This does // not fit in with the new context based concurrency priority in HalDeviceManager, // but we cannot break existing API's. So, we artificially boost the priority of // the request by "faking" the requestor context as settings app. // We probably need some UI dialog to allow the user to grant the app's LOHS // request. Once that UI dialog is added, we can get rid of this hack and use the UI // to elevate the priority of LOHS request only if user approves the request to // toggle wifi off for LOHS. requestorWs = mFrameworkFacade.getSettingsWorkSource(mContext); } finally { Binder.restoreCallingIdentity(ident); } } else { if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) { throw new SecurityException(TAG + ": Permission denied"); } // Already privileged, no need to fake. requestorWs = new WorkSource(uid, packageName); } // verify that tethering is not disabled if (mUserManager.hasUserRestrictionForUser( UserManager.DISALLOW_CONFIG_TETHERING, UserHandle.getUserHandleForUid(uid))) { return LocalOnlyHotspotCallback.ERROR_TETHERING_DISALLOWED; } // the app should be in the foreground long ident = Binder.clearCallingIdentity(); try { // also need to verify that Locations services are enabled. if (!mFrameworkFacade.isAppForeground(mContext, uid)) { return LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE; } } finally { Binder.restoreCallingIdentity(ident); } // check if we are currently tethering if (!mActiveModeWarden.canRequestMoreSoftApManagers(requestorWs) && mTetheredSoftApTracker.getState() == WIFI_AP_STATE_ENABLED) { // Tethering is enabled, cannot start LocalOnlyHotspot mLog.info("Cannot start localOnlyHotspot when WiFi Tethering is active.") .flush(); return LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE; } // now create the new LOHS request info object LocalOnlyHotspotRequestInfo request = new LocalOnlyHotspotRequestInfo( requestorWs, callback, new LocalOnlyRequestorCallback(), customConfig); return mLohsSoftApTracker.start(pid, request); } /** * see {@link WifiManager#stopLocalOnlyHotspot()} * * @throws SecurityException if the caller does not have permission to stop a Local Only * Hotspot. */ @Override public void stopLocalOnlyHotspot() { // don't do a permission check here. if the app's permission to change the wifi state is // revoked, we still want them to be able to stop a previously created hotspot (otherwise // it could cost the user money). When the app created the hotspot, its permission was // checked. final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); mLog.info("stopLocalOnlyHotspot uid=% pid=%").c(uid).c(pid).flush(); mLohsSoftApTracker.stopByPid(pid); } /** * see {@link WifiManager#watchLocalOnlyHotspot(LocalOnlyHotspotObserver)} * * This call requires the android.permission.NETWORK_SETTINGS permission. * * @param callback Callback to communicate with WifiManager and allow cleanup if the app dies. * * @throws SecurityException if the caller does not have permission to watch Local Only Hotspot * status updates. * @throws IllegalStateException if the caller attempts to watch LocalOnlyHotspot updates with * an existing subscription. */ @Override public void startWatchLocalOnlyHotspot(ILocalOnlyHotspotCallback callback) { // NETWORK_SETTINGS is a signature only permission. enforceNetworkSettingsPermission(); throw new UnsupportedOperationException("LocalOnlyHotspot is still in development"); } /** * see {@link WifiManager#unregisterLocalOnlyHotspotObserver()} */ @Override public void stopWatchLocalOnlyHotspot() { // NETWORK_STACK is a signature only permission. enforceNetworkSettingsPermission(); throw new UnsupportedOperationException("LocalOnlyHotspot is still in development"); } /** * see {@link WifiManager#getWifiApConfiguration()} * @return soft access point configuration * @throws SecurityException if the caller does not have permission to retrieve the softap * config */ @Nullable @Override public WifiConfiguration getWifiApConfiguration() { enforceAccessPermission(); int uid = Binder.getCallingUid(); // only allow Settings UI to get the saved SoftApConfig if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid)) { // random apps should not be allowed to read the user specified config throw new SecurityException("App not allowed to read or update stored WiFi Ap config " + "(uid = " + uid + ")"); } if (isVerboseLoggingEnabled()) { mLog.info("getWifiApConfiguration uid=%").c(uid).flush(); } // hand off work to the ClientModeImpl handler thread to sync work between calls // and SoftApManager starting up softap return (mWifiThreadRunner.call(mWifiApConfigStore::getApConfiguration, new SoftApConfiguration.Builder().build())).toWifiConfiguration(); } /** * see {@link WifiManager#getSoftApConfiguration()} * @return soft access point configuration {@link SoftApConfiguration} * @throws SecurityException if the caller does not have permission to retrieve the softap * config */ @NonNull @Override public SoftApConfiguration getSoftApConfiguration() { int uid = Binder.getCallingUid(); if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid) && !mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) { // random apps should not be allowed to read the user specified config throw new SecurityException("App not allowed to read or update stored WiFi Ap config " + "(uid = " + uid + ")"); } if (isVerboseLoggingEnabled()) { mLog.info("getSoftApConfiguration uid=%").c(uid).flush(); } // hand off work to the ClientModeImpl handler thread to sync work between calls // and SoftApManager starting up softap return mWifiThreadRunner.call(mWifiApConfigStore::getApConfiguration, new SoftApConfiguration.Builder().build()); } /** * see {@link WifiManager#setWifiApConfiguration(WifiConfiguration)} * @param wifiConfig WifiConfiguration details for soft access point * @return boolean indicating success or failure of the operation * @throws SecurityException if the caller does not have permission to write the softap config */ @Override public boolean setWifiApConfiguration(WifiConfiguration wifiConfig, String packageName) { if (enforceChangePermission(packageName) != MODE_ALLOWED) { return false; } int uid = Binder.getCallingUid(); // only allow Settings UI to write the stored SoftApConfig if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid)) { // random apps should not be allowed to read the user specified config throw new SecurityException("App not allowed to read or update stored WiFi AP config " + "(uid = " + uid + ")"); } mLog.info("setWifiApConfiguration uid=%").c(uid).flush(); if (wifiConfig == null) return false; SoftApConfiguration softApConfig = ApConfigUtil.fromWifiConfiguration(wifiConfig); if (softApConfig == null) return false; if (WifiApConfigStore.validateApWifiConfiguration( softApConfig, false, mContext)) { mWifiThreadRunner.post(() -> mWifiApConfigStore.setApConfiguration(softApConfig)); return true; } else { Log.e(TAG, "Invalid WifiConfiguration"); return false; } } /** * see {@link WifiManager#setSoftApConfiguration(SoftApConfiguration)} * @param softApConfig {@link SoftApConfiguration} details for soft access point * @return boolean indicating success or failure of the operation * @throws SecurityException if the caller does not have permission to write the softap config */ @Override public boolean setSoftApConfiguration( @NonNull SoftApConfiguration softApConfig, @NonNull String packageName) { int uid = Binder.getCallingUid(); boolean privileged = mWifiPermissionsUtil.checkNetworkSettingsPermission(uid); if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid) && !privileged) { // random apps should not be allowed to read the user specified config throw new SecurityException("App not allowed to read or update stored WiFi Ap config " + "(uid = " + uid + ")"); } mLog.info("setSoftApConfiguration uid=%").c(uid).flush(); if (softApConfig == null) return false; if (WifiApConfigStore.validateApWifiConfiguration(softApConfig, privileged, mContext)) { mActiveModeWarden.updateSoftApConfiguration(softApConfig); mWifiThreadRunner.post(() -> mWifiApConfigStore.setApConfiguration(softApConfig)); return true; } else { Log.e(TAG, "Invalid SoftAp Configuration"); return false; } } /** * see {@link android.net.wifi.WifiManager#setScanAlwaysAvailable(boolean)} */ @Override public void setScanAlwaysAvailable(boolean isAvailable, String packageName) { enforceNetworkSettingsPermission(); mLog.info("setScanAlwaysAvailable uid=% package=% isAvailable=%") .c(Binder.getCallingUid()) .c(packageName) .c(isAvailable) .flush(); mSettingsStore.handleWifiScanAlwaysAvailableToggled(isAvailable); long ident = Binder.clearCallingIdentity(); try { mWifiInjector.getWifiScanAlwaysAvailableSettingsCompatibility() .handleWifiScanAlwaysAvailableToggled(isAvailable); } finally { Binder.restoreCallingIdentity(ident); } mActiveModeWarden.scanAlwaysModeChanged(); } /** * see {@link android.net.wifi.WifiManager#isScanAlwaysAvailable()} */ @Override public boolean isScanAlwaysAvailable() { enforceAccessPermission(); if (isVerboseLoggingEnabled()) { mLog.info("isScanAlwaysAvailable uid=%").c(Binder.getCallingUid()).flush(); } return mSettingsStore.isScanAlwaysAvailableToggleEnabled(); } /** * see {@link android.net.wifi.WifiManager#disconnect()} */ @Override public boolean disconnect(String packageName) { if (enforceChangePermission(packageName) != MODE_ALLOWED) { return false; } if (!isTargetSdkLessThanQOrPrivileged( packageName, Binder.getCallingPid(), Binder.getCallingUid())) { mLog.info("disconnect not allowed for uid=%") .c(Binder.getCallingUid()).flush(); return false; } mLog.info("disconnect uid=%").c(Binder.getCallingUid()).flush(); mWifiThreadRunner.post(() -> mActiveModeWarden.getPrimaryClientModeManager().disconnect()); return true; } /** * see {@link android.net.wifi.WifiManager#reconnect()} */ @Override public boolean reconnect(String packageName) { if (enforceChangePermission(packageName) != MODE_ALLOWED) { return false; } int callingUid = Binder.getCallingUid(); if (!isTargetSdkLessThanQOrPrivileged(packageName, Binder.getCallingPid(), callingUid)) { mLog.info("reconnect not allowed for uid=%").c(callingUid).flush(); return false; } mLog.info("reconnect uid=%").c(callingUid).flush(); mWifiThreadRunner.post(() -> { mActiveModeWarden.getPrimaryClientModeManager().reconnect(new WorkSource(callingUid)); }); return true; } /** * see {@link android.net.wifi.WifiManager#reassociate()} */ @Override public boolean reassociate(String packageName) { if (enforceChangePermission(packageName) != MODE_ALLOWED) { return false; } if (!isTargetSdkLessThanQOrPrivileged( packageName, Binder.getCallingPid(), Binder.getCallingUid())) { mLog.info("reassociate not allowed for uid=%") .c(Binder.getCallingUid()).flush(); return false; } mLog.info("reassociate uid=%").c(Binder.getCallingUid()).flush(); mWifiThreadRunner.post(() -> mActiveModeWarden.getPrimaryClientModeManager().reassociate()); return true; } /** * Returns true if we should log the call to getSupportedFeatures. * * Because of the way getSupportedFeatures is used in WifiManager, there are * often clusters of several back-to-back calls; avoid repeated logging if * the feature set has not changed and the time interval is short. */ private boolean needToLogSupportedFeatures(long features) { if (isVerboseLoggingEnabled()) { long now = mClock.getElapsedSinceBootMillis(); synchronized (this) { if (now > mLastLoggedSupportedFeaturesTimestamp + A_FEW_MILLISECONDS || features != mLastLoggedSupportedFeatures) { mLastLoggedSupportedFeaturesTimestamp = now; mLastLoggedSupportedFeatures = features; return true; } } } return false; } private static final int A_FEW_MILLISECONDS = 250; private long mLastLoggedSupportedFeatures = -1; private long mLastLoggedSupportedFeaturesTimestamp = 0; /** * see {@link android.net.wifi.WifiManager#getSupportedFeatures} */ @Override public long getSupportedFeatures() { enforceAccessPermission(); long features = getSupportedFeaturesInternal(); if (needToLogSupportedFeatures(features)) { mLog.info("getSupportedFeatures uid=% returns %") .c(Binder.getCallingUid()) .c(Long.toHexString(features)) .flush(); } return features; } @Override public void getWifiActivityEnergyInfoAsync(IOnWifiActivityEnergyInfoListener listener) { if (isVerboseLoggingEnabled()) { mLog.info("getWifiActivityEnergyInfoAsync uid=%") .c(Binder.getCallingUid()) .flush(); } // getWifiActivityEnergyInfo() performs permission checking WifiActivityEnergyInfo info = getWifiActivityEnergyInfo(); try { listener.onWifiActivityEnergyInfo(info); } catch (RemoteException e) { Log.e(TAG, "onWifiActivityEnergyInfo: RemoteException -- ", e); } } private WifiActivityEnergyInfo getWifiActivityEnergyInfo() { enforceAccessPermission(); if (isVerboseLoggingEnabled()) { mLog.info("getWifiActivityEnergyInfo uid=%").c(Binder.getCallingUid()).flush(); } if ((getSupportedFeatures() & WifiManager.WIFI_FEATURE_LINK_LAYER_STATS) == 0) { return null; } WifiLinkLayerStats stats = mWifiThreadRunner.call( () -> mActiveModeWarden.getPrimaryClientModeManager().getWifiLinkLayerStats(), null); if (stats == null) { return null; } final long rxIdleTimeMillis = stats.on_time - stats.tx_time - stats.rx_time; if (VDBG || rxIdleTimeMillis < 0 || stats.on_time < 0 || stats.tx_time < 0 || stats.rx_time < 0 || stats.on_time_scan < 0) { Log.d(TAG, " getWifiActivityEnergyInfo: " + " on_time_millis=" + stats.on_time + " tx_time_millis=" + stats.tx_time + " rx_time_millis=" + stats.rx_time + " rxIdleTimeMillis=" + rxIdleTimeMillis + " scan_time_millis=" + stats.on_time_scan); } // Convert the LinkLayerStats into WifiActivityEnergyInfo return new WifiActivityEnergyInfo( mClock.getElapsedSinceBootMillis(), WifiActivityEnergyInfo.STACK_STATE_STATE_IDLE, stats.tx_time, stats.rx_time, stats.on_time_scan, rxIdleTimeMillis); } /** * see {@link android.net.wifi.WifiManager#getConfiguredNetworks()} * * @param packageName String name of the calling package * @param featureId The feature in the package * @param callerNetworksOnly Whether to only return networks created by the caller * @return the list of configured networks */ @Override public ParceledListSlice getConfiguredNetworks(String packageName, String featureId, boolean callerNetworksOnly) { enforceAccessPermission(); int callingUid = Binder.getCallingUid(); // bypass shell: can get various pkg name // also bypass if caller is only retrieving networks added by itself if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) { mWifiPermissionsUtil.checkPackage(callingUid, packageName); if (!callerNetworksOnly) { long ident = Binder.clearCallingIdentity(); try { mWifiPermissionsUtil.enforceCanAccessScanResults(packageName, featureId, callingUid, null); } catch (SecurityException e) { Log.w(TAG, "Permission violation - getConfiguredNetworks not allowed for uid=" + callingUid + ", packageName=" + packageName + ", reason=" + e); return new ParceledListSlice<>(new ArrayList<>()); } finally { Binder.restoreCallingIdentity(ident); } } } boolean isDeviceOrProfileOwner = isDeviceOrProfileOwner(callingUid, packageName); boolean isCarrierApp = mWifiInjector.makeTelephonyManager() .checkCarrierPrivilegesForPackageAnyPhone(packageName) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; boolean isPrivileged = isPrivileged(getCallingPid(), callingUid); // Only DO, PO, carrier app or system app can use callerNetworksOnly argument if (callerNetworksOnly) { if (!isDeviceOrProfileOwner && !isCarrierApp && !isPrivileged) { throw new SecurityException( "Not a DO, PO, carrier or privileged app"); } } boolean isTargetSdkLessThanQOrPrivileged = isTargetSdkLessThanQOrPrivileged( packageName, Binder.getCallingPid(), callingUid); if (!isTargetSdkLessThanQOrPrivileged && !isCarrierApp) { mLog.info("getConfiguredNetworks not allowed for uid=%") .c(callingUid).flush(); return new ParceledListSlice<>(new ArrayList<>()); } if (isVerboseLoggingEnabled()) { mLog.info("getConfiguredNetworks uid=%").c(callingUid).flush(); } int targetConfigUid = Process.INVALID_UID; // don't expose any MAC addresses if (isPrivileged || isDeviceOrProfileOwner) { targetConfigUid = Process.WIFI_UID; // expose all MAC addresses } else if (isCarrierApp) { targetConfigUid = callingUid; // expose only those configs created by the Carrier App } int finalTargetConfigUid = targetConfigUid; List configs = mWifiThreadRunner.call( () -> mWifiConfigManager.getSavedNetworks(finalTargetConfigUid), Collections.emptyList()); if (isTargetSdkLessThanQOrPrivileged && !callerNetworksOnly) { return new ParceledListSlice<>( WifiConfigurationUtil.convertMultiTypeConfigsToLegacyConfigs(configs)); } // Should only get its own configs List creatorConfigs = new ArrayList<>(); for (WifiConfiguration config : configs) { if (config.creatorUid == callingUid) { creatorConfigs.add(config); } } return new ParceledListSlice<>( WifiConfigurationUtil.convertMultiTypeConfigsToLegacyConfigs(creatorConfigs)); } /** * see {@link android.net.wifi.WifiManager#getPrivilegedConfiguredNetworks()} * * @param packageName String name of the calling package * @param featureId The feature in the package * @return the list of configured networks with real preSharedKey */ @Override public ParceledListSlice getPrivilegedConfiguredNetworks( String packageName, String featureId) { enforceReadCredentialPermission(); enforceAccessPermission(); int callingUid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); try { mWifiPermissionsUtil.enforceCanAccessScanResults(packageName, featureId, callingUid, null); } catch (SecurityException e) { Log.w(TAG, "Permission violation - getPrivilegedConfiguredNetworks not allowed for" + " uid=" + callingUid + ", packageName=" + packageName + ", reason=" + e); return null; } finally { Binder.restoreCallingIdentity(ident); } if (isVerboseLoggingEnabled()) { mLog.info("getPrivilegedConfiguredNetworks uid=%").c(callingUid).flush(); } List configs = mWifiThreadRunner.call( () -> mWifiConfigManager.getConfiguredNetworksWithPasswords(), Collections.emptyList()); return new ParceledListSlice<>( WifiConfigurationUtil.convertMultiTypeConfigsToLegacyConfigs(configs)); } /** * Return a map of all matching configurations keys with corresponding scanResults (or an empty * map if none). * * @param scanResults The list of scan results * @return Map that consists of FQDN (Fully Qualified Domain Name) and corresponding * scanResults per network type({@link WifiManager#PASSPOINT_HOME_NETWORK} and {@link * WifiManager#PASSPOINT_ROAMING_NETWORK}). */ @Override public Map>> getAllMatchingPasspointProfilesForScanResults(List scanResults) { if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) { throw new SecurityException(TAG + ": Permission denied"); } if (isVerboseLoggingEnabled()) { mLog.info("getMatchingPasspointConfigurations uid=%").c(Binder.getCallingUid()).flush(); } if (!ScanResultUtil.validateScanResultList(scanResults)) { Log.e(TAG, "Attempt to retrieve passpoint with invalid scanResult List"); return Collections.emptyMap(); } return mWifiThreadRunner.call( () -> mPasspointManager.getAllMatchingPasspointProfilesForScanResults(scanResults), Collections.emptyMap()); } /** * Returns list of OSU (Online Sign-Up) providers associated with the given list of ScanResult. * * @param scanResults a list of ScanResult that has Passpoint APs. * @return Map that consists of {@link OsuProvider} and a matching list of {@link ScanResult}. */ @Override public Map> getMatchingOsuProviders( List scanResults) { if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) { throw new SecurityException(TAG + ": Permission denied"); } if (isVerboseLoggingEnabled()) { mLog.info("getMatchingOsuProviders uid=%").c(Binder.getCallingUid()).flush(); } if (!ScanResultUtil.validateScanResultList(scanResults)) { Log.w(TAG, "Attempt to retrieve OsuProviders with invalid scanResult List"); return Collections.emptyMap(); } return mWifiThreadRunner.call( () -> mPasspointManager.getMatchingOsuProviders(scanResults), Collections.emptyMap()); } /** * Returns the matching Passpoint configurations for given OSU(Online Sign-Up) providers. * * @param osuProviders a list of {@link OsuProvider} * @return Map that consists of {@link OsuProvider} and matching {@link PasspointConfiguration}. */ @Override public Map getMatchingPasspointConfigsForOsuProviders( List osuProviders) { if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) { throw new SecurityException(TAG + ": Permission denied"); } if (isVerboseLoggingEnabled()) { mLog.info("getMatchingPasspointConfigsForOsuProviders uid=%").c( Binder.getCallingUid()).flush(); } if (osuProviders == null) { Log.e(TAG, "Attempt to retrieve Passpoint configuration with null osuProviders"); return new HashMap<>(); } return mWifiThreadRunner.call( () -> mPasspointManager.getMatchingPasspointConfigsForOsuProviders(osuProviders), Collections.emptyMap()); } /** * Returns the corresponding wifi configurations for given FQDN (Fully Qualified Domain Name) * list. * * An empty list will be returned when no match is found. * * @param fqdnList a list of FQDN * @return List of {@link WifiConfiguration} converted from {@link PasspointProvider} */ @Override public List getWifiConfigsForPasspointProfiles(List fqdnList) { if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) { throw new SecurityException(TAG + ": Permission denied"); } if (isVerboseLoggingEnabled()) { mLog.info("getWifiConfigsForPasspointProfiles uid=%").c( Binder.getCallingUid()).flush(); } if (fqdnList == null) { Log.e(TAG, "Attempt to retrieve WifiConfiguration with null fqdn List"); return new ArrayList<>(); } return mWifiThreadRunner.call( () -> mPasspointManager.getWifiConfigsForPasspointProfiles(fqdnList), Collections.emptyList()); } /** * Returns a list of Wifi configurations for matched available WifiNetworkSuggestion * corresponding to the given scan results. * * An empty list will be returned when no match is found or all matched suggestions is not * available(not allow user manually connect, user not approved or open network). * * @param scanResults a list of {@link ScanResult}. * @return a list of {@link WifiConfiguration} from matched {@link WifiNetworkSuggestion}. */ @Override public List getWifiConfigForMatchedNetworkSuggestionsSharedWithUser( List scanResults) { if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) { throw new SecurityException(TAG + ": Permission denied"); } if (isVerboseLoggingEnabled()) { mLog.info("getWifiConfigsForMatchedNetworkSuggestions uid=%").c( Binder.getCallingUid()).flush(); } if (!ScanResultUtil.validateScanResultList(scanResults)) { Log.w(TAG, "Attempt to retrieve WifiConfiguration with invalid scanResult List"); return new ArrayList<>(); } return mWifiThreadRunner.call( () -> mWifiNetworkSuggestionsManager .getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(scanResults), Collections.emptyList()); } /** * see {@link WifiManager#addNetworkPrivileged(WifiConfiguration)} * @return WifiManager.AddNetworkResult Object. */ @Override public @NonNull WifiManager.AddNetworkResult addOrUpdateNetworkPrivileged( WifiConfiguration config, String packageName) { int pid = Binder.getCallingPid(); int uid = Binder.getCallingUid(); mWifiPermissionsUtil.checkPackage(uid, packageName); boolean hasPermission = isPrivileged(pid, uid) || isDeviceOrProfileOwner(uid, packageName) || mWifiPermissionsUtil.isSystem(packageName, uid); if (!hasPermission) { throw new SecurityException("Caller is not a device owner, profile owner, system app," + " or privileged app"); } if (config != null) { config.networkId = removeSecurityTypeFromNetworkId(config.networkId); } return addOrUpdateNetworkInternal(config, packageName, uid); } /** * see {@link android.net.wifi.WifiManager#addOrUpdateNetwork(WifiConfiguration)} * @return the supplicant-assigned identifier for the new or updated * network if the operation succeeds, or {@code -1} if it fails */ @Override public int addOrUpdateNetwork(WifiConfiguration config, String packageName) { if (enforceChangePermission(packageName) != MODE_ALLOWED) { return -1; } int callingUid = Binder.getCallingUid(); if (!isTargetSdkLessThanQOrPrivileged( packageName, Binder.getCallingPid(), callingUid)) { mLog.info("addOrUpdateNetwork not allowed for uid=%") .c(Binder.getCallingUid()).flush(); return -1; } if (config != null) { config.networkId = removeSecurityTypeFromNetworkId(config.networkId); } mLog.info("addOrUpdateNetwork uid=%").c(Binder.getCallingUid()).flush(); return addOrUpdateNetworkInternal(config, packageName, callingUid).networkId; } private @NonNull AddNetworkResult addOrUpdateNetworkInternal(WifiConfiguration config, String packageName, int callingUid) { if (config == null) { Log.e(TAG, "bad network configuration"); return new AddNetworkResult( AddNetworkResult.STATUS_INVALID_CONFIGURATION, -1); } mWifiMetrics.incrementNumAddOrUpdateNetworkCalls(); // Previously, this API is overloaded for installing Passpoint profiles. Now // that we have a dedicated API for doing it, redirect the call to the dedicated API. if (config.isPasspoint()) { PasspointConfiguration passpointConfig = PasspointProvider.convertFromWifiConfig(config); if (passpointConfig == null || passpointConfig.getCredential() == null) { Log.e(TAG, "Missing credential for Passpoint profile"); return new AddNetworkResult( AddNetworkResult.STATUS_ADD_PASSPOINT_FAILURE, -1); } // Copy over certificates and keys. X509Certificate[] x509Certificates = null; if (config.enterpriseConfig.getCaCertificate() != null) { x509Certificates = new X509Certificate[]{config.enterpriseConfig.getCaCertificate()}; } passpointConfig.getCredential().setCaCertificates(x509Certificates); passpointConfig.getCredential().setClientCertificateChain( config.enterpriseConfig.getClientCertificateChain()); passpointConfig.getCredential().setClientPrivateKey( config.enterpriseConfig.getClientPrivateKey()); if (!addOrUpdatePasspointConfiguration(passpointConfig, packageName)) { Log.e(TAG, "Failed to add Passpoint profile"); return new AddNetworkResult( AddNetworkResult.STATUS_ADD_PASSPOINT_FAILURE, -1); } // There is no network ID associated with a Passpoint profile. return new AddNetworkResult(AddNetworkResult.STATUS_SUCCESS, 0); } if (config.isEnterprise() && config.enterpriseConfig.isEapMethodServerCertUsed() && !config.enterpriseConfig.isMandatoryParameterSetForServerCertValidation()) { if (!(mContext.getResources().getBoolean( R.bool.config_wifiAllowInsecureEnterpriseConfigurationsForSettingsAndSUW) && isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid()))) { Log.e(TAG, "Enterprise network configuration is missing either a Root CA " + "or a domain name"); return new AddNetworkResult( AddNetworkResult.STATUS_INVALID_CONFIGURATION_ENTERPRISE, -1); } Log.w(TAG, "Insecure Enterprise network " + config.SSID + " configured by Settings/SUW"); } Log.i("addOrUpdateNetworkInternal", " uid = " + Binder.getCallingUid() + " SSID " + config.SSID + " nid=" + config.networkId); // TODO: b/171981339, add more detailed failure reason into // WifiConfigManager.NetworkUpdateResult, and plumb that reason up. int networkId = mWifiThreadRunner.call( () -> mWifiConfigManager.addOrUpdateNetwork(config, callingUid, packageName) .getNetworkId(), WifiConfiguration.INVALID_NETWORK_ID); if (networkId >= 0) { return new AddNetworkResult(AddNetworkResult.STATUS_SUCCESS, addSecurityTypeToNetworkId( networkId, config.getDefaultSecurityParams().getSecurityType())); } return new AddNetworkResult( AddNetworkResult.STATUS_ADD_WIFI_CONFIG_FAILURE, -1); } public static void verifyCert(X509Certificate caCert) throws GeneralSecurityException, IOException { CertificateFactory factory = CertificateFactory.getInstance("X.509"); CertPathValidator validator = CertPathValidator.getInstance(CertPathValidator.getDefaultType()); CertPath path = factory.generateCertPath( Arrays.asList(caCert)); KeyStore ks = KeyStore.getInstance("AndroidCAStore"); ks.load(null, null); PKIXParameters params = new PKIXParameters(ks); params.setRevocationEnabled(false); validator.validate(path, params); } /** * See {@link android.net.wifi.WifiManager#removeNetwork(int)} * @param netId the integer that identifies the network configuration * to the supplicant * @return {@code true} if the operation succeeded */ @Override public boolean removeNetwork(int netId, String packageName) { if (enforceChangePermission(packageName) != MODE_ALLOWED) { return false; } if (!isTargetSdkLessThanQOrPrivileged( packageName, Binder.getCallingPid(), Binder.getCallingUid())) { mLog.info("removeNetwork not allowed for uid=%") .c(Binder.getCallingUid()).flush(); return false; } final int internalNetId = removeSecurityTypeFromNetworkId(netId); int callingUid = Binder.getCallingUid(); mLog.info("removeNetwork uid=%").c(callingUid).flush(); return mWifiThreadRunner.call( () -> mWifiConfigManager.removeNetwork(internalNetId, callingUid, packageName), false); } @Override public boolean removeNonCallerConfiguredNetworks(String packageName) { if (enforceChangePermission(packageName) != MODE_ALLOWED) { throw new SecurityException("Caller does not hold CHANGE_WIFI_STATE permission"); } final int callingUid = Binder.getCallingUid(); if (!mWifiPermissionsUtil.isDeviceOwner(callingUid, packageName)) { throw new SecurityException("Caller is not device owner"); } return mWifiThreadRunner.call( () -> mWifiConfigManager.removeNonCallerConfiguredNetwork(callingUid), false); } /** * Trigger a connect request and wait for the callback to return status. * This preserves the legacy connect API behavior, i.e. {@link WifiManager#enableNetwork( * int, true)} * @return */ private boolean triggerConnectAndReturnStatus(int netId, int callingUid) { final CountDownLatch countDownLatch = new CountDownLatch(1); final Mutable success = new Mutable<>(false); IActionListener.Stub connectListener = new IActionListener.Stub() { @Override public void onSuccess() { success.value = true; countDownLatch.countDown(); } @Override public void onFailure(int reason) { success.value = false; countDownLatch.countDown(); } }; mWifiThreadRunner.post(() -> mMakeBeforeBreakManager.stopAllSecondaryTransientClientModeManagers(() -> mConnectHelper.connectToNetwork( new NetworkUpdateResult(netId), new ActionListenerWrapper(connectListener), callingUid) ) ); // now wait for response. try { countDownLatch.await(RUN_WITH_SCISSORS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Log.e(TAG, "Failed to retrieve connect status"); } return success.value; } /** * See {@link android.net.wifi.WifiManager#enableNetwork(int, boolean)} * @param netId the integer that identifies the network configuration * to the supplicant * @param disableOthers if true, disable all other networks. * @return {@code true} if the operation succeeded */ @Override public boolean enableNetwork(int netId, boolean disableOthers, String packageName) { if (enforceChangePermission(packageName) != MODE_ALLOWED) { return false; } if (!isTargetSdkLessThanQOrPrivileged( packageName, Binder.getCallingPid(), Binder.getCallingUid())) { mLog.info("enableNetwork not allowed for uid=%") .c(Binder.getCallingUid()).flush(); return false; } final int internalNetId = removeSecurityTypeFromNetworkId(netId); int callingUid = Binder.getCallingUid(); // TODO b/33807876 Log netId mLog.info("enableNetwork uid=% disableOthers=%") .c(callingUid) .c(disableOthers).flush(); mWifiMetrics.incrementNumEnableNetworkCalls(); if (disableOthers) { return triggerConnectAndReturnStatus(internalNetId, callingUid); } else { return mWifiThreadRunner.call( () -> mWifiConfigManager.enableNetwork( internalNetId, false, callingUid, packageName), false); } } /** * See {@link android.net.wifi.WifiManager#disableNetwork(int)} * @param netId the integer that identifies the network configuration * to the supplicant * @return {@code true} if the operation succeeded */ @Override public boolean disableNetwork(int netId, String packageName) { if (enforceChangePermission(packageName) != MODE_ALLOWED) { return false; } if (!isTargetSdkLessThanQOrPrivileged( packageName, Binder.getCallingPid(), Binder.getCallingUid())) { mLog.info("disableNetwork not allowed for uid=%") .c(Binder.getCallingUid()).flush(); return false; } final int internalNetId = removeSecurityTypeFromNetworkId(netId); int callingUid = Binder.getCallingUid(); mLog.info("disableNetwork uid=%").c(callingUid).flush(); return mWifiThreadRunner.call( () -> mWifiConfigManager.disableNetwork( internalNetId, callingUid, packageName), false); } /** * See * {@link android.net.wifi.WifiManager#startRestrictingAutoJoinToSubscriptionId(int)} * @param subscriptionId the subscription ID of the carrier whose merged wifi networks won't be * disabled. */ @Override @RequiresApi(Build.VERSION_CODES.S) public void startRestrictingAutoJoinToSubscriptionId(int subscriptionId) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) { throw new SecurityException(TAG + ": Permission denied"); } mLog.info("startRestrictingAutoJoinToSubscriptionId=% uid=%").c(subscriptionId) .c(Binder.getCallingUid()).flush(); mWifiThreadRunner.post(() -> { mWifiConfigManager .startRestrictingAutoJoinToSubscriptionId(subscriptionId); // always disconnect here and rely on auto-join to find the appropriate carrier network // to join. Even if we are currently connected to the carrier-merged wifi, it's still // better to disconnect here because it's possible that carrier wifi offload is // disabled. mActiveModeWarden.getPrimaryClientModeManager().disconnect(); }); } /** * See {@link android.net.wifi.WifiManager#stopRestrictingAutoJoinToSubscriptionId()} */ @Override @RequiresApi(Build.VERSION_CODES.S) public void stopRestrictingAutoJoinToSubscriptionId() { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) { throw new SecurityException(TAG + ": Permission denied"); } mLog.info("stopRestrictingAutoJoinToSubscriptionId uid=%") .c(Binder.getCallingUid()).flush(); mWifiThreadRunner.post(() -> mWifiConfigManager.stopRestrictingAutoJoinToSubscriptionId()); } /** * See {@link android.net.wifi.WifiManager#allowAutojoinGlobal(boolean)} * @param choice the OEM's choice to allow auto-join */ @Override public void allowAutojoinGlobal(boolean choice) { enforceNetworkSettingsPermission(); int callingUid = Binder.getCallingUid(); mLog.info("allowAutojoinGlobal=% uid=%").c(choice).c(callingUid).flush(); mWifiThreadRunner.post(() -> mWifiConnectivityManager.setAutoJoinEnabledExternal(choice)); mLastCallerInfoManager.put(LastCallerInfoManager.AUTOJOIN_GLOBAL, Process.myTid(), callingUid, Binder.getCallingPid(), "", choice); } /** * See {@link android.net.wifi.WifiManager#allowAutojoin(int, boolean)} * @param netId the integer that identifies the network configuration * @param choice the user's choice to allow auto-join */ @Override public void allowAutojoin(int netId, boolean choice) { enforceNetworkSettingsPermission(); final int internalNetId = removeSecurityTypeFromNetworkId(netId); int callingUid = Binder.getCallingUid(); mLog.info("allowAutojoin=% uid=%").c(choice).c(callingUid).flush(); mWifiThreadRunner.post(() -> { WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(internalNetId); if (config == null) { return; } if (config.fromWifiNetworkSpecifier) { Log.e(TAG, "Auto-join configuration is not permitted for NetworkSpecifier " + "connections: " + config); return; } if (config.isPasspoint() && !config.isEphemeral()) { Log.e(TAG, "Auto-join configuration for a non-ephemeral Passpoint network should be " + "configured using FQDN: " + config); return; } // If the network is a suggestion, store the auto-join configure to the // WifiNetWorkSuggestionsManager. if (config.fromWifiNetworkSuggestion) { if (!mWifiNetworkSuggestionsManager .allowNetworkSuggestionAutojoin(config, choice)) { return; } } // even for Suggestion, modify the current ephemeral configuration so that // existing configuration auto-connection is updated correctly if (choice != config.allowAutojoin) { mWifiConfigManager.allowAutojoin(internalNetId, choice); // do not log this metrics for passpoint networks again here since it's already // logged in PasspointManager. if (!config.isPasspoint()) { mWifiMetrics.logUserActionEvent(choice ? UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_ON : UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_OFF, internalNetId); } } }); } /** * See {@link android.net.wifi.WifiManager#allowAutojoinPasspoint(String, boolean)} * @param fqdn the FQDN that identifies the passpoint configuration * @param enableAutojoin true to enable auto-join, false to disable */ @Override public void allowAutojoinPasspoint(String fqdn, boolean enableAutojoin) { enforceNetworkSettingsPermission(); if (fqdn == null) { throw new IllegalArgumentException("FQDN cannot be null"); } int callingUid = Binder.getCallingUid(); mLog.info("allowAutojoinPasspoint=% uid=%").c(enableAutojoin).c(callingUid).flush(); mWifiThreadRunner.post( () -> mPasspointManager.enableAutojoin(null, fqdn, enableAutojoin)); } /** * See {@link android.net.wifi.WifiManager * #setMacRandomizationSettingPasspointEnabled(String, boolean)} * @param fqdn the FQDN that identifies the passpoint configuration * @param enable true to enable mac randomization, false to disable */ @Override public void setMacRandomizationSettingPasspointEnabled(String fqdn, boolean enable) { enforceNetworkSettingsPermission(); if (fqdn == null) { throw new IllegalArgumentException("FQDN cannot be null"); } int callingUid = Binder.getCallingUid(); mLog.info("setMacRandomizationSettingPasspointEnabled=% uid=%") .c(enable).c(callingUid).flush(); mWifiThreadRunner.post( () -> mPasspointManager.enableMacRandomization(fqdn, enable)); } /** * See {@link android.net.wifi.WifiManager#setPasspointMeteredOverride(String, boolean)} * @param fqdn the FQDN that identifies the passpoint configuration * @param meteredOverride One of the values in {@link MeteredOverride} */ @Override public void setPasspointMeteredOverride(String fqdn, int meteredOverride) { enforceNetworkSettingsPermission(); if (fqdn == null) { throw new IllegalArgumentException("FQDN cannot be null"); } int callingUid = Binder.getCallingUid(); mLog.info("setPasspointMeteredOverride=% uid=%") .c(meteredOverride).c(callingUid).flush(); mWifiThreadRunner.post( () -> mPasspointManager.setMeteredOverride(fqdn, meteredOverride)); } /** * Provides backward compatibility for apps using * {@link WifiManager#getConnectionInfo()}, {@link WifiManager#getDhcpInfo()} when a * secondary STA is created as a result of a request from their app (peer to peer * WifiNetworkSpecifier request or oem paid/private suggestion). */ private ClientModeManager getClientModeManagerIfSecondaryCmmRequestedByCallerPresent( int callingUid, @NonNull String callingPackageName) { List secondaryCmms = mActiveModeWarden.getClientModeManagersInRoles( ROLE_CLIENT_LOCAL_ONLY, ROLE_CLIENT_SECONDARY_LONG_LIVED); for (ConcreteClientModeManager cmm : secondaryCmms) { WorkSource reqWs = cmm.getRequestorWs(); // If there are more than 1 secondary CMM for same app, return any one (should not // happen currently since we don't support 3 STA's concurrently). if (reqWs.equals(new WorkSource(callingUid, callingPackageName))) { mLog.info("getConnectionInfo providing secondary CMM info").flush(); return cmm; } } // No secondary CMM's created for the app, return primary CMM. return mActiveModeWarden.getPrimaryClientModeManager(); } /** * See {@link android.net.wifi.WifiManager#getConnectionInfo()} * @return the Wi-Fi information, contained in {@link WifiInfo}. */ @Override public WifiInfo getConnectionInfo(String callingPackage, String callingFeatureId) { enforceAccessPermission(); int uid = Binder.getCallingUid(); if (isVerboseLoggingEnabled()) { mLog.info("getConnectionInfo uid=%").c(uid).flush(); } long ident = Binder.clearCallingIdentity(); try { WifiInfo wifiInfo = mWifiThreadRunner.call( () -> getClientModeManagerIfSecondaryCmmRequestedByCallerPresent( uid, callingPackage) .syncRequestConnectionInfo(), new WifiInfo()); long redactions = wifiInfo.getApplicableRedactions(); if (mWifiPermissionsUtil.checkLocalMacAddressPermission(uid)) { if (isVerboseLoggingEnabled()) { Log.v(TAG, "Clearing REDACT_FOR_LOCAL_MAC_ADDRESS for " + callingPackage + "(uid=" + uid + ")"); } redactions &= ~NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS; } if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) { if (isVerboseLoggingEnabled()) { Log.v(TAG, "Clearing REDACT_FOR_NETWORK_SETTINGS for " + callingPackage + "(uid=" + uid + ")"); } redactions &= ~NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS; } try { if (isVerboseLoggingEnabled()) { Log.v(TAG, "Clearing REDACT_FOR_ACCESS_FINE_LOCATION for " + callingPackage + "(uid=" + uid + ")"); } mWifiPermissionsUtil.enforceCanAccessScanResults(callingPackage, callingFeatureId, uid, null); redactions &= ~NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION; } catch (SecurityException ignored) { if (isVerboseLoggingEnabled()) { Log.v(TAG, "Keeping REDACT_FOR_ACCESS_FINE_LOCATION:" + ignored); } } WifiInfo wifiInfoCopy = wifiInfo.makeCopy(redactions); wifiInfoCopy.setNetworkId(addSecurityTypeToNetworkId(wifiInfoCopy.getNetworkId(), convertWifiInfoSecurityTypeToWifiConfiguration( wifiInfoCopy.getCurrentSecurityType()))); return wifiInfoCopy; } finally { Binder.restoreCallingIdentity(ident); } } /** * Return the results of the most recent access point scan, in the form of * a list of {@link ScanResult} objects. * @return the list of results */ @Override public List getScanResults(String callingPackage, String callingFeatureId) { enforceAccessPermission(); int uid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); if (isVerboseLoggingEnabled()) { mLog.info("getScanResults uid=%").c(uid).flush(); } try { mWifiPermissionsUtil.enforceCanAccessScanResults(callingPackage, callingFeatureId, uid, null); List scanResults = mWifiThreadRunner.call( mScanRequestProxy::getScanResults, Collections.emptyList()); return scanResults; } catch (SecurityException e) { Log.w(TAG, "Permission violation - getScanResults not allowed for uid=" + uid + ", packageName=" + callingPackage + ", reason=" + e); return new ArrayList<>(); } finally { Binder.restoreCallingIdentity(ident); } } /** * Return the filtered ScanResults which may be authenticated by the suggested network * configurations. * @return The map of {@link WifiNetworkSuggestion} and the list of {@link ScanResult} which * may be authenticated by the corresponding network configuration. */ @Override @NonNull public Map> getMatchingScanResults( @NonNull List networkSuggestions, @Nullable List scanResults, String callingPackage, String callingFeatureId) { enforceAccessPermission(); int uid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); try { mWifiPermissionsUtil.enforceCanAccessScanResults(callingPackage, callingFeatureId, uid, null); return mWifiThreadRunner.call( () -> { if (!ScanResultUtil.validateScanResultList(scanResults)) { return mWifiNetworkSuggestionsManager.getMatchingScanResults( networkSuggestions, mScanRequestProxy.getScanResults()); } else { return mWifiNetworkSuggestionsManager.getMatchingScanResults( networkSuggestions, scanResults); } }, Collections.emptyMap()); } catch (SecurityException e) { Log.w(TAG, "Permission violation - getMatchingScanResults not allowed for uid=" + uid + ", packageName=" + callingPackage + ", reason + e"); } finally { Binder.restoreCallingIdentity(ident); } return Collections.emptyMap(); } /** * Add or update a Passpoint configuration. * * @param config The Passpoint configuration to be added * @return true on success or false on failure */ @Override public boolean addOrUpdatePasspointConfiguration( PasspointConfiguration config, String packageName) { if (enforceChangePermission(packageName) != MODE_ALLOWED) { return false; } int callingUid = Binder.getCallingUid(); if (!isTargetSdkLessThanROrPrivileged( packageName, Binder.getCallingPid(), callingUid)) { mLog.info("addOrUpdatePasspointConfiguration not allowed for uid=%") .c(Binder.getCallingUid()).flush(); return false; } mLog.info("addorUpdatePasspointConfiguration uid=%").c(callingUid).flush(); return mWifiThreadRunner.call( () -> mPasspointManager.addOrUpdateProvider(config, callingUid, packageName, false, true), false); } /** * Remove the Passpoint configuration identified by its FQDN (Fully Qualified Domain Name). * * @param fqdn The FQDN of the Passpoint configuration to be removed * @return true on success or false on failure */ @Override public boolean removePasspointConfiguration(String fqdn, String packageName) { return removePasspointConfigurationInternal(fqdn, null); } /** * Remove a Passpoint profile based on either FQDN (multiple matching profiles) or a unique * identifier (one matching profile). * * @param fqdn The FQDN of the Passpoint configuration to be removed * @param uniqueId The unique identifier of the Passpoint configuration to be removed * @return true on success or false on failure */ private boolean removePasspointConfigurationInternal(String fqdn, String uniqueId) { final int uid = Binder.getCallingUid(); boolean privileged = false; if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid) || mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(uid)) { privileged = true; } mLog.info("removePasspointConfigurationInternal uid=%").c(Binder.getCallingUid()).flush(); final boolean privilegedFinal = privileged; return mWifiThreadRunner.call( () -> mPasspointManager.removeProvider(uid, privilegedFinal, uniqueId, fqdn), false); } /** * Return the list of the installed Passpoint configurations. * * An empty list will be returned when no configuration is installed. * @param packageName String name of the calling package * @return A list of {@link PasspointConfiguration}. */ @Override public List getPasspointConfigurations(String packageName) { final int uid = Binder.getCallingUid(); boolean privileged = false; if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid) || mWifiPermissionsUtil.checkNetworkSetupWizardPermission(uid)) { privileged = true; } if (isVerboseLoggingEnabled()) { mLog.info("getPasspointConfigurations uid=%").c(Binder.getCallingUid()).flush(); } final boolean privilegedFinal = privileged; return mWifiThreadRunner.call( () -> mPasspointManager.getProviderConfigs(uid, privilegedFinal), Collections.emptyList()); } /** * Query for a Hotspot 2.0 release 2 OSU icon * @param bssid The BSSID of the AP * @param fileName Icon file name */ @Override public void queryPasspointIcon(long bssid, String fileName) { enforceAccessPermission(); mLog.info("queryPasspointIcon uid=%").c(Binder.getCallingUid()).flush(); mWifiThreadRunner.post(() -> { mActiveModeWarden.getPrimaryClientModeManager().syncQueryPasspointIcon(bssid, fileName); }); } /** * Match the currently associated network against the SP matching the given FQDN * @param fqdn FQDN of the SP * @return ordinal [HomeProvider, RoamingProvider, Incomplete, None, Declined] */ @Override public int matchProviderWithCurrentNetwork(String fqdn) { mLog.info("matchProviderWithCurrentNetwork uid=%").c(Binder.getCallingUid()).flush(); return 0; } /** * Get the country code * @return Get the best choice country code for wifi, regardless of if it was set or * not. * Returns null when there is no country code available. */ @Override public String getCountryCode() { enforceNetworkSettingsPermission(); if (isVerboseLoggingEnabled()) { mLog.info("getCountryCode uid=%").c(Binder.getCallingUid()).flush(); } return mCountryCode.getCountryCode(); } /** * Set the Wifi country code. This call will override the country code set by telephony. * @param countryCode A 2-Character alphanumeric country code. * */ @RequiresApi(Build.VERSION_CODES.S) @Override public void setOverrideCountryCode(@NonNull String countryCode) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } mContext.enforceCallingOrSelfPermission( Manifest.permission.MANAGE_WIFI_COUNTRY_CODE, "WifiService"); if (!WifiCountryCode.isValid(countryCode)) { throw new IllegalArgumentException("Country code must be a 2-Character alphanumeric" + " code. But got countryCode " + countryCode + " instead"); } if (isVerboseLoggingEnabled()) { mLog.info("setOverrideCountryCode uid=% countryCode=%") .c(Binder.getCallingUid()).c(countryCode).flush(); } // Post operation to handler thread mWifiThreadRunner.post(() -> mCountryCode.setOverrideCountryCode(countryCode)); } /** * Clear the country code previously set through setOverrideCountryCode method. * */ @RequiresApi(Build.VERSION_CODES.S) @Override public void clearOverrideCountryCode() { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } mContext.enforceCallingOrSelfPermission( Manifest.permission.MANAGE_WIFI_COUNTRY_CODE, "WifiService"); if (isVerboseLoggingEnabled()) { mLog.info("clearCountryCode uid=%").c(Binder.getCallingUid()).flush(); } // Post operation to handler thread mWifiThreadRunner.post(() -> mCountryCode.clearOverrideCountryCode()); } /** * Change the default country code previously set from ro.boot.wificountrycode. * @param countryCode A 2-Character alphanumeric country code. * */ @RequiresApi(Build.VERSION_CODES.S) @Override public void setDefaultCountryCode(@NonNull String countryCode) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } mContext.enforceCallingOrSelfPermission( Manifest.permission.MANAGE_WIFI_COUNTRY_CODE, "WifiService"); if (!WifiCountryCode.isValid(countryCode)) { throw new IllegalArgumentException("Country code must be a 2-Character alphanumeric" + " code. But got countryCode " + countryCode + " instead"); } if (isVerboseLoggingEnabled()) { mLog.info("setDefaultCountryCode uid=% countryCode=%") .c(Binder.getCallingUid()).c(countryCode).flush(); } // Post operation to handler thread mWifiThreadRunner.post(() -> mCountryCode.setDefaultCountryCode(countryCode)); } @Override public boolean is24GHzBandSupported() { if (isVerboseLoggingEnabled()) { mLog.info("is24GHzBandSupported uid=%").c(Binder.getCallingUid()).flush(); } return is24GhzBandSupportedInternal(); } private boolean is24GhzBandSupportedInternal() { if (mContext.getResources().getBoolean(R.bool.config_wifi24ghzSupport)) { return true; } return mWifiThreadRunner.call( () -> mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ).length > 0, false); } @Override public boolean is5GHzBandSupported() { if (isVerboseLoggingEnabled()) { mLog.info("is5GHzBandSupported uid=%").c(Binder.getCallingUid()).flush(); } return is5GhzBandSupportedInternal(); } private boolean is5GhzBandSupportedInternal() { if (mContext.getResources().getBoolean(R.bool.config_wifi5ghzSupport)) { return true; } return mWifiThreadRunner.call( () -> mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ).length > 0, false); } @Override public boolean is6GHzBandSupported() { if (isVerboseLoggingEnabled()) { mLog.info("is6GHzBandSupported uid=%").c(Binder.getCallingUid()).flush(); } return is6GhzBandSupportedInternal(); } private boolean is6GhzBandSupportedInternal() { if (mContext.getResources().getBoolean(R.bool.config_wifi6ghzSupport)) { return true; } return mWifiThreadRunner.call( () -> mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_6_GHZ).length > 0, false); } @Override public boolean is60GHzBandSupported() { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } if (isVerboseLoggingEnabled()) { mLog.info("is60GHzBandSupported uid=%").c(Binder.getCallingUid()).flush(); } return is60GhzBandSupportedInternal(); } private boolean is60GhzBandSupportedInternal() { if (mContext.getResources().getBoolean(R.bool.config_wifi60ghzSupport)) { return true; } return mWifiThreadRunner.call( () -> mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_60_GHZ).length > 0, false); } @Override public boolean isWifiStandardSupported(@WifiStandard int standard) { return mWifiThreadRunner.call( () -> mActiveModeWarden.getPrimaryClientModeManager().isWifiStandardSupported( standard), false); } /** * Return the DHCP-assigned addresses from the last successful DHCP request, * if any. * @return the DHCP information * @deprecated */ @Override public DhcpInfo getDhcpInfo(@NonNull String packageName) { enforceAccessPermission(); int callingUid = Binder.getCallingUid(); if (isVerboseLoggingEnabled()) { mLog.info("getDhcpInfo uid=%").c(callingUid).flush(); } DhcpResultsParcelable dhcpResults = mWifiThreadRunner.call( () -> getClientModeManagerIfSecondaryCmmRequestedByCallerPresent( callingUid, packageName) .syncGetDhcpResultsParcelable(), new DhcpResultsParcelable()); DhcpInfo info = new DhcpInfo(); if (dhcpResults.baseConfiguration != null) { if (dhcpResults.baseConfiguration.getIpAddress() != null && dhcpResults.baseConfiguration.getIpAddress().getAddress() instanceof Inet4Address) { info.ipAddress = Inet4AddressUtils.inet4AddressToIntHTL( (Inet4Address) dhcpResults.baseConfiguration.getIpAddress().getAddress()); } if (dhcpResults.baseConfiguration.getGateway() != null) { info.gateway = Inet4AddressUtils.inet4AddressToIntHTL( (Inet4Address) dhcpResults.baseConfiguration.getGateway()); } int dnsFound = 0; for (InetAddress dns : dhcpResults.baseConfiguration.getDnsServers()) { if (dns instanceof Inet4Address) { if (dnsFound == 0) { info.dns1 = Inet4AddressUtils.inet4AddressToIntHTL((Inet4Address) dns); } else { info.dns2 = Inet4AddressUtils.inet4AddressToIntHTL((Inet4Address) dns); } if (++dnsFound > 1) break; } } } String serverAddress = dhcpResults.serverAddress; if (serverAddress != null) { InetAddress serverInetAddress = InetAddresses.parseNumericAddress(serverAddress); info.serverAddress = Inet4AddressUtils.inet4AddressToIntHTL((Inet4Address) serverInetAddress); } info.leaseDuration = dhcpResults.leaseDuration; return info; } /** * enable TDLS for the local NIC to remote NIC * The APPs don't know the remote MAC address to identify NIC though, * so we need to do additional work to find it from remote IP address */ private static class TdlsTaskParams { String mRemoteIpAddress; boolean mEnable; } private class TdlsTask extends AsyncTask { @Override protected Integer doInBackground(TdlsTaskParams... params) { // Retrieve parameters for the call TdlsTaskParams param = params[0]; String remoteIpAddress = param.mRemoteIpAddress.trim(); boolean enable = param.mEnable; // Get MAC address of Remote IP String macAddress = null; try (BufferedReader reader = new BufferedReader(new FileReader("/proc/net/arp"))) { // Skip over the line bearing column titles reader.readLine(); String line; while ((line = reader.readLine()) != null) { String[] tokens = line.split("[ ]+"); if (tokens.length < 6) { continue; } // ARP column format is // Address HWType HWAddress Flags Mask IFace String ip = tokens[0]; String mac = tokens[3]; if (remoteIpAddress.equals(ip)) { macAddress = mac; break; } } if (macAddress == null) { Log.w(TAG, "Did not find remoteAddress {" + remoteIpAddress + "} in " + "/proc/net/arp"); } else { enableTdlsWithMacAddress(macAddress, enable); } } catch (FileNotFoundException e) { Log.e(TAG, "Could not open /proc/net/arp to lookup mac address"); } catch (IOException e) { Log.e(TAG, "Could not read /proc/net/arp to lookup mac address"); } return 0; } } @Override public void enableTdls(String remoteAddress, boolean enable) { if (remoteAddress == null) { throw new IllegalArgumentException("remoteAddress cannot be null"); } mLog.info("enableTdls uid=% enable=%").c(Binder.getCallingUid()).c(enable).flush(); TdlsTaskParams params = new TdlsTaskParams(); params.mRemoteIpAddress = remoteAddress; params.mEnable = enable; new TdlsTask().execute(params); } @Override public void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable) { mLog.info("enableTdlsWithMacAddress uid=% enable=%") .c(Binder.getCallingUid()) .c(enable) .flush(); if (remoteMacAddress == null) { throw new IllegalArgumentException("remoteMacAddress cannot be null"); } mWifiThreadRunner.post(() -> mActiveModeWarden.getPrimaryClientModeManager().enableTdls( remoteMacAddress, enable)); } /** * Temporarily disable a network, should be trigger when user disconnect a network */ @Override public void disableEphemeralNetwork(String network, String packageName) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, "WifiService"); if (!isPrivileged(Binder.getCallingPid(), Binder.getCallingUid())) { mLog.info("disableEphemeralNetwork not allowed for uid=%") .c(Binder.getCallingUid()).flush(); return; } mLog.info("disableEphemeralNetwork uid=%").c(Binder.getCallingUid()).flush(); mWifiThreadRunner.post(() -> mWifiConfigManager.userTemporarilyDisabledNetwork(network, Binder.getCallingUid())); } private void removeAppStateInternal(int uid, @NonNull String pkgName) { ApplicationInfo ai = new ApplicationInfo(); ai.packageName = pkgName; ai.uid = uid; mWifiConfigManager.removeNetworksForApp(ai); mScanRequestProxy.clearScanRequestTimestampsForApp(pkgName, uid); // Remove all suggestions from the package. mWifiNetworkSuggestionsManager.removeApp(pkgName); mWifiInjector.getWifiNetworkFactory().removeUserApprovedAccessPointsForApp( pkgName); // Remove all Passpoint profiles from package. mWifiInjector.getPasspointManager().removePasspointProviderWithPackage( pkgName); } private void registerForBroadcasts() { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); intentFilter.addDataScheme("package"); mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); Uri uri = intent.getData(); if (uid == -1 || uri == null) { Log.e(TAG, "Uid or Uri is missing for action:" + intent.getAction()); return; } String pkgName = uri.getSchemeSpecificPart(); PackageManager pm = context.getPackageManager(); PackageInfo packageInfo = null; try { packageInfo = pm.getPackageInfo(pkgName, 0); } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Couldn't get PackageInfo for package:" + pkgName); } // If package is not removed or disabled, just ignore. if (packageInfo != null && packageInfo.applicationInfo != null && packageInfo.applicationInfo.enabled) { return; } Log.d(TAG, "Remove settings for package:" + pkgName); // Call the method in the main Wifi thread. mWifiThreadRunner.post(() -> { removeAppStateInternal(uid, pkgName); }); } }, intentFilter); } private void registerForCarrierConfigChange() { IntentFilter filter = new IntentFilter(); filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final int subId = SubscriptionManager.getActiveDataSubscriptionId(); // post operation to handler thread mWifiThreadRunner.post(() -> { Log.d(TAG, "ACTION_CARRIER_CONFIG_CHANGED, active subId: " + subId); mTetheredSoftApTracker.updateSoftApCapabilityWhenCarrierConfigChanged(subId); mActiveModeWarden.updateSoftApCapability( mTetheredSoftApTracker.getSoftApCapability()); }); } }, filter); WifiPhoneStateListener phoneStateListener = new WifiPhoneStateListener( mWifiInjector.getWifiHandlerThread().getLooper()); mContext.getSystemService(TelephonyManager.class).listen( phoneStateListener, PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); } @Override public int handleShellCommand(@NonNull ParcelFileDescriptor in, @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, @NonNull String[] args) { WifiShellCommand shellCommand = new WifiShellCommand(mWifiInjector, this, mContext, mWifiGlobals, mWifiThreadRunner); return shellCommand.exec(this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args); } private void updateWifiMetrics() { mWifiThreadRunner.run(() -> { mWifiMetrics.updateSavedNetworks( mWifiConfigManager.getSavedNetworks(Process.WIFI_UID)); mActiveModeWarden.updateMetrics(); mPasspointManager.updateMetrics(); }); boolean isEnhancedMacRandEnabled = mFrameworkFacade.getIntegerSetting(mContext, WifiConfigManager.ENHANCED_MAC_RANDOMIZATION_FEATURE_FORCE_ENABLE_FLAG, 0) == 1 ? true : false; mWifiMetrics.setEnhancedMacRandomizationForceEnabled(isEnhancedMacRandEnabled); mWifiMetrics.setIsScanningAlwaysEnabled( mSettingsStore.isScanAlwaysAvailableToggleEnabled()); mWifiMetrics.setVerboseLoggingEnabled(isVerboseLoggingEnabled()); mWifiMetrics.setWifiWakeEnabled(mWifiInjector.getWakeupController().isEnabled()); } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PERMISSION_GRANTED) { pw.println("Permission Denial: can't dump WifiService from from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); return; } if (args != null && args.length > 0 && WifiMetrics.PROTO_DUMP_ARG.equals(args[0])) { // WifiMetrics proto bytes were requested. Dump only these. updateWifiMetrics(); mWifiMetrics.dump(fd, pw, args); } else if (args != null && args.length > 0 && IpClientUtil.DUMP_ARG.equals(args[0])) { // IpClient dump was requested. Pass it along and take no further action. String[] ipClientArgs = new String[args.length - 1]; System.arraycopy(args, 1, ipClientArgs, 0, ipClientArgs.length); mActiveModeWarden.getPrimaryClientModeManager().dumpIpClient(fd, pw, ipClientArgs); } else if (args != null && args.length > 0 && WifiScoreReport.DUMP_ARG.equals(args[0])) { mActiveModeWarden.getPrimaryClientModeManager().dumpWifiScoreReport(fd, pw, args); } else if (args != null && args.length > 0 && WifiScoreCard.DUMP_ARG.equals(args[0])) { WifiScoreCard wifiScoreCard = mWifiInjector.getWifiScoreCard(); String networkListBase64 = mWifiThreadRunner.call(() -> wifiScoreCard.getNetworkListBase64(true), ""); pw.println(networkListBase64); } else { pw.println("Verbose logging is " + (isVerboseLoggingEnabled() ? "on" : "off")); pw.println("Stay-awake conditions: " + mFacade.getIntegerSetting(mContext, Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0)); pw.println("mInIdleMode " + mInIdleMode); pw.println("mScanPending " + mScanPending); pw.println("SettingsStore:"); mSettingsStore.dump(fd, pw, args); mActiveModeWarden.dump(fd, pw, args); mMakeBeforeBreakManager.dump(fd, pw, args); pw.println(); mWifiTrafficPoller.dump(fd, pw, args); pw.println(); pw.println("Locks held:"); mWifiLockManager.dump(pw); pw.println(); mWifiMulticastLockManager.dump(pw); pw.println(); WifiScoreCard wifiScoreCard = mWifiInjector.getWifiScoreCard(); String networkListBase64 = mWifiThreadRunner.call(() -> wifiScoreCard.getNetworkListBase64(true), ""); pw.println("WifiScoreCard:"); pw.println(networkListBase64); updateWifiMetrics(); mWifiMetrics.dump(fd, pw, args); pw.println(); mWifiThreadRunner.run(() -> mWifiNetworkSuggestionsManager.dump(fd, pw, args)); pw.println(); mWifiBackupRestore.dump(fd, pw, args); pw.println(); pw.println("ScoringParams: " + mWifiInjector.getScoringParams()); pw.println(); mWifiThreadRunner.run(() -> { mWifiInjector.getWifiNetworkScoreCache().dumpWithLatestScanResults( fd, pw, args, mScanRequestProxy.getScanResults()); mWifiInjector.getSettingsConfigStore().dump(fd, pw, args); }); pw.println(); mCountryCode.dump(fd, pw, args); mWifiInjector.getWifiNetworkFactory().dump(fd, pw, args); mWifiInjector.getUntrustedWifiNetworkFactory().dump(fd, pw, args); mWifiInjector.getOemWifiNetworkFactory().dump(fd, pw, args); pw.println("Wlan Wake Reasons:" + mWifiNative.getWlanWakeReasonCount()); pw.println(); mWifiConfigManager.dump(fd, pw, args); pw.println(); mPasspointManager.dump(pw); pw.println(); mWifiInjector.getWifiDiagnostics().captureBugReportData( WifiDiagnostics.REPORT_REASON_USER_ACTION); mWifiInjector.getWifiDiagnostics().dump(fd, pw, args); mWifiConnectivityManager.dump(fd, pw, args); mWifiThreadRunner.run(() -> { mWifiHealthMonitor.dump(fd, pw, args); }); mWifiThreadRunner.run(() -> { mWifiScoreCard.dump(fd, pw, args); }); mWifiInjector.getWakeupController().dump(fd, pw, args); mWifiInjector.getWifiLastResortWatchdog().dump(fd, pw, args); mWifiInjector.getAdaptiveConnectivityEnabledSettingObserver().dump(fd, pw, args); mWifiInjector.getWifiGlobals().dump(fd, pw, args); mWifiInjector.getSarManager().dump(fd, pw, args); pw.println(); mLastCallerInfoManager.dump(pw); pw.println(); mWifiInjector.getLinkProbeManager().dump(fd, pw, args); } } @Override public boolean acquireWifiLock(IBinder binder, int lockMode, String tag, WorkSource ws) { mLog.info("acquireWifiLock uid=% lockMode=%") .c(Binder.getCallingUid()) .c(lockMode).flush(); // Check on permission to make this call mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); // If no UID is provided in worksource, use the calling UID WorkSource updatedWs = (ws == null || ws.isEmpty()) ? new WorkSource(Binder.getCallingUid()) : ws; if (!WifiLockManager.isValidLockMode(lockMode)) { throw new IllegalArgumentException("lockMode =" + lockMode); } return mWifiThreadRunner.call(() -> mWifiLockManager.acquireWifiLock(lockMode, tag, binder, updatedWs), false); } @Override public void updateWifiLockWorkSource(IBinder binder, WorkSource ws) { mLog.info("updateWifiLockWorkSource uid=%").c(Binder.getCallingUid()).flush(); // Check on permission to make this call mContext.enforceCallingOrSelfPermission( android.Manifest.permission.UPDATE_DEVICE_STATS, null); // If no UID is provided in worksource, use the calling UID WorkSource updatedWs = (ws == null || ws.isEmpty()) ? new WorkSource(Binder.getCallingUid()) : ws; mWifiThreadRunner.run(() -> mWifiLockManager.updateWifiLockWorkSource(binder, updatedWs)); } @Override public boolean releaseWifiLock(IBinder binder) { mLog.info("releaseWifiLock uid=%").c(Binder.getCallingUid()).flush(); // Check on permission to make this call mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); return mWifiThreadRunner.call(() -> mWifiLockManager.releaseWifiLock(binder), false); } @Override public void initializeMulticastFiltering() { enforceMulticastChangePermission(); mLog.info("initializeMulticastFiltering uid=%").c(Binder.getCallingUid()).flush(); mWifiMulticastLockManager.initializeFiltering(); } @Override public void acquireMulticastLock(IBinder binder, String tag) { enforceMulticastChangePermission(); mLog.info("acquireMulticastLock uid=%").c(Binder.getCallingUid()).flush(); mWifiMulticastLockManager.acquireLock(binder, tag); } @Override public void releaseMulticastLock(String tag) { enforceMulticastChangePermission(); mLog.info("releaseMulticastLock uid=%").c(Binder.getCallingUid()).flush(); mWifiMulticastLockManager.releaseLock(tag); } @Override public boolean isMulticastEnabled() { enforceAccessPermission(); if (isVerboseLoggingEnabled()) { mLog.info("isMulticastEnabled uid=%").c(Binder.getCallingUid()).flush(); } return mWifiMulticastLockManager.isMulticastEnabled(); } @Override public void enableVerboseLogging(int verbose) { enforceAccessPermission(); enforceNetworkSettingsPermission(); mLog.info("enableVerboseLogging uid=% verbose=%") .c(Binder.getCallingUid()) .c(verbose).flush(); boolean enabled = verbose > 0; mWifiInjector.getSettingsConfigStore().put(WIFI_VERBOSE_LOGGING_ENABLED, enabled); onVerboseLoggingStatusChanged(enabled); enableVerboseLoggingInternal(verbose); } private void onVerboseLoggingStatusChanged(boolean enabled) { int itemCount = mRegisteredWifiLoggingStatusListeners.beginBroadcast(); for (int i = 0; i < itemCount; i++) { try { mRegisteredWifiLoggingStatusListeners.getBroadcastItem(i) .onStatusChanged(enabled); } catch (RemoteException e) { Log.e(TAG, "onVerboseLoggingStatusChanged: RemoteException -- ", e); } } mRegisteredWifiLoggingStatusListeners.finishBroadcast(); } private boolean isVerboseLoggingEnabled() { return WifiManager.VERBOSE_LOGGING_LEVEL_DISABLED != mVerboseLoggingLevel; } private void enableVerboseLoggingInternal(int verbose) { if (verbose > WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED && mBuildProperties.isUserBuild()) { throw new SecurityException(TAG + ": Not allowed for the user build."); } mVerboseLoggingLevel = verbose; // Update wifi globals before sending the verbose logging change. mWifiThreadRunner.removeCallbacks(mAutoDisableShowKeyVerboseLoggingModeRunnable); if (WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY == mVerboseLoggingLevel) { mWifiGlobals.setShowKeyVerboseLoggingModeEnabled(true); mWifiThreadRunner.postDelayed(mAutoDisableShowKeyVerboseLoggingModeRunnable, AUTO_DISABLE_SHOW_KEY_COUNTDOWN_MILLIS); } else { // Ensure the show key mode is disabled. mWifiGlobals.setShowKeyVerboseLoggingModeEnabled(false); } mActiveModeWarden.enableVerboseLogging(isVerboseLoggingEnabled()); mWifiLockManager.enableVerboseLogging(verbose); mWifiMulticastLockManager.enableVerboseLogging(verbose); mWifiInjector.enableVerboseLogging(verbose); mWifiInjector.getSarManager().enableVerboseLogging(verbose); } @Override public int getVerboseLoggingLevel() { if (isVerboseLoggingEnabled()) { mLog.info("getVerboseLoggingLevel uid=%").c(Binder.getCallingUid()).flush(); } return mVerboseLoggingLevel; } private Runnable mAutoDisableShowKeyVerboseLoggingModeRunnable = new Runnable() { @Override public void run() { // If still enabled, fallback to the regular verbose logging mode. if (isVerboseLoggingEnabled()) { enableVerboseLoggingInternal(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED); } } }; @Override public void factoryReset(String packageName) { enforceNetworkSettingsPermission(); if (enforceChangePermission(packageName) != MODE_ALLOWED) { return; } mLog.info("factoryReset uid=%").c(Binder.getCallingUid()).flush(); if (mUserManager.hasUserRestrictionForUser( UserManager.DISALLOW_NETWORK_RESET, UserHandle.getUserHandleForUid(Binder.getCallingUid()))) { return; } if (!mUserManager.hasUserRestrictionForUser( UserManager.DISALLOW_CONFIG_TETHERING, UserHandle.getUserHandleForUid(Binder.getCallingUid()))) { // Turn mobile hotspot off stopSoftApInternal(WifiManager.IFACE_IP_MODE_UNSPECIFIED); } if (mUserManager.hasUserRestrictionForUser( UserManager.DISALLOW_CONFIG_WIFI, UserHandle.getUserHandleForUid(Binder.getCallingUid()))) { return; } // Delete all Wifi SSIDs List networks = mWifiThreadRunner.call( () -> mWifiConfigManager.getSavedNetworks(Process.WIFI_UID), Collections.emptyList()); for (WifiConfiguration network : networks) { removeNetwork(network.networkId, packageName); } // Delete all Passpoint configurations List configs = mWifiThreadRunner.call( () -> mPasspointManager.getProviderConfigs(Process.WIFI_UID /* ignored */, true), Collections.emptyList()); for (PasspointConfiguration config : configs) { removePasspointConfigurationInternal(null, config.getUniqueId()); } mWifiThreadRunner.post(() -> { mPasspointManager.clearAnqpRequestsAndFlushCache(); mWifiConfigManager.clearUserTemporarilyDisabledList(); mWifiConfigManager.removeAllEphemeralOrPasspointConfiguredNetworks(); mWifiInjector.getWifiNetworkFactory().clear(); mWifiNetworkSuggestionsManager.clear(); mWifiInjector.getWifiScoreCard().clear(); mWifiHealthMonitor.clear(); mWifiCarrierInfoManager.clear(); notifyFactoryReset(); }); } /** * Notify the Factory Reset Event to application who may installed wifi configurations. */ private void notifyFactoryReset() { Intent intent = new Intent(WifiManager.ACTION_NETWORK_SETTINGS_RESET); // Retrieve list of broadcast receivers for this broadcast & send them directed broadcasts // to wake them up (if they're in background). List resolveInfos = mContext.getPackageManager().queryBroadcastReceiversAsUser( intent, 0, UserHandle.of(mWifiInjector.getWifiPermissionsWrapper().getCurrentUser())); if (resolveInfos == null || resolveInfos.isEmpty()) return; // No need to send broadcast. for (ResolveInfo resolveInfo : resolveInfos) { Intent intentToSend = new Intent(intent); intentToSend.setComponent(new ComponentName( resolveInfo.activityInfo.applicationInfo.packageName, resolveInfo.activityInfo.name)); mContext.sendBroadcastAsUser(intentToSend, UserHandle.CURRENT, android.Manifest.permission.NETWORK_CARRIER_PROVISIONING); } } @Override public Network getCurrentNetwork() { if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) { throw new SecurityException(TAG + ": Permission denied"); } if (isVerboseLoggingEnabled()) { mLog.info("getCurrentNetwork uid=%").c(Binder.getCallingUid()).flush(); } return getPrimaryClientModeManagerBlockingThreadSafe().syncGetCurrentNetwork(); } public static String toHexString(String s) { if (s == null) { return "null"; } StringBuilder sb = new StringBuilder(); sb.append('\'').append(s).append('\''); for (int n = 0; n < s.length(); n++) { sb.append(String.format(" %02x", s.charAt(n) & 0xffff)); } return sb.toString(); } /** * Retrieve the data to be backed to save the current state. * * @return Raw byte stream of the data to be backed up. */ @Override public byte[] retrieveBackupData() { enforceNetworkSettingsPermission(); mLog.info("retrieveBackupData uid=%").c(Binder.getCallingUid()).flush(); Log.d(TAG, "Retrieving backup data"); List wifiConfigurations = mWifiThreadRunner.call( () -> mWifiConfigManager.getConfiguredNetworksWithPasswords(), null); byte[] backupData = mWifiBackupRestore.retrieveBackupDataFromConfigurations(wifiConfigurations); Log.d(TAG, "Retrieved backup data"); return backupData; } /** * Helper method to restore networks retrieved from backup data. * * @param configurations list of WifiConfiguration objects parsed from the backup data. */ private void restoreNetworks(List configurations) { if (configurations == null) { Log.e(TAG, "Backup data parse failed"); return; } int callingUid = Binder.getCallingUid(); mWifiThreadRunner.run( () -> { for (WifiConfiguration configuration : configurations) { int networkId = mWifiConfigManager.addOrUpdateNetwork(configuration, callingUid) .getNetworkId(); if (networkId == WifiConfiguration.INVALID_NETWORK_ID) { Log.e(TAG, "Restore network failed: " + configuration.getProfileKey()); continue; } // Enable all networks restored. mWifiConfigManager.enableNetwork(networkId, false, callingUid, null); // Restore auto-join param. mWifiConfigManager.allowAutojoin(networkId, configuration.allowAutojoin); } }); } /** * Restore state from the backed up data. * * @param data Raw byte stream of the backed up data. */ @Override public void restoreBackupData(byte[] data) { enforceNetworkSettingsPermission(); mLog.info("restoreBackupData uid=%").c(Binder.getCallingUid()).flush(); Log.d(TAG, "Restoring backup data"); List wifiConfigurations = mWifiBackupRestore.retrieveConfigurationsFromBackupData(data); restoreNetworks(wifiConfigurations); Log.d(TAG, "Restored backup data"); } /** * Retrieve the soft ap config data to be backed to save current config data. * * @return Raw byte stream of the data to be backed up. */ @Override public byte[] retrieveSoftApBackupData() { enforceNetworkSettingsPermission(); mLog.info("retrieveSoftApBackupData uid=%").c(Binder.getCallingUid()).flush(); SoftApConfiguration config = mWifiThreadRunner.call(mWifiApConfigStore::getApConfiguration, new SoftApConfiguration.Builder().build()); byte[] backupData = mSoftApBackupRestore.retrieveBackupDataFromSoftApConfiguration(config); Log.d(TAG, "Retrieved soft ap backup data"); return backupData; } /** * Restore soft ap config from the backed up data. * * @param data Raw byte stream of the backed up data. * @return restored SoftApConfiguration or Null if data is invalid. */ @Override public SoftApConfiguration restoreSoftApBackupData(byte[] data) { enforceNetworkSettingsPermission(); mLog.info("restoreSoftApBackupData uid=%").c(Binder.getCallingUid()).flush(); SoftApConfiguration softApConfig = mSoftApBackupRestore.retrieveSoftApConfigurationFromBackupData(data); if (softApConfig != null) { mWifiThreadRunner.post(() -> mWifiApConfigStore.setApConfiguration( mWifiApConfigStore.resetToDefaultForUnsupportedConfig( mWifiApConfigStore.upgradeSoftApConfiguration(softApConfig)))); Log.d(TAG, "Restored soft ap backup data"); } return softApConfig; } /** * Restore state from the older supplicant back up data. * The old backup data was essentially a backup of wpa_supplicant.conf & ipconfig.txt file. * * @param supplicantData Raw byte stream of wpa_supplicant.conf * @param ipConfigData Raw byte stream of ipconfig.txt */ public void restoreSupplicantBackupData(byte[] supplicantData, byte[] ipConfigData) { enforceNetworkSettingsPermission(); mLog.trace("restoreSupplicantBackupData uid=%").c(Binder.getCallingUid()).flush(); Log.d(TAG, "Restoring supplicant backup data"); List wifiConfigurations = mWifiBackupRestore.retrieveConfigurationsFromSupplicantBackupData( supplicantData, ipConfigData); restoreNetworks(wifiConfigurations); Log.d(TAG, "Restored supplicant backup data"); } /** * Starts subscription provisioning with a provider. * * @param provider {@link OsuProvider} the provider to provision with * @param callback {@link IProvisioningCallback} the callback object to inform status */ @Override public void startSubscriptionProvisioning(OsuProvider provider, IProvisioningCallback callback) { if (provider == null) { throw new IllegalArgumentException("Provider must not be null"); } if (callback == null) { throw new IllegalArgumentException("Callback must not be null"); } if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) { throw new SecurityException(TAG + ": Permission denied"); } final int uid = Binder.getCallingUid(); mLog.trace("startSubscriptionProvisioning uid=%").c(uid).flush(); if (getPrimaryClientModeManagerBlockingThreadSafe() .syncStartSubscriptionProvisioning(uid, provider, callback)) { mLog.trace("Subscription provisioning started with %") .c(provider.toString()).flush(); } } /** * See * {@link WifiManager#registerTrafficStateCallback(Executor, WifiManager.TrafficStateCallback)} * * @param callback Traffic State callback to register * * @throws SecurityException if the caller does not have permission to register a callback * @throws RemoteException if remote exception happens * @throws IllegalArgumentException if the arguments are null or invalid */ @Override public void registerTrafficStateCallback(ITrafficStateCallback callback) { // verify arguments if (callback == null) { throw new IllegalArgumentException("Callback must not be null"); } enforceNetworkSettingsPermission(); if (isVerboseLoggingEnabled()) { mLog.info("registerTrafficStateCallback uid=%").c(Binder.getCallingUid()).flush(); } // Post operation to handler thread mWifiThreadRunner.post(() -> mWifiTrafficPoller.addCallback(callback)); } /** * see {@link android.net.wifi.WifiManager#unregisterTrafficStateCallback( * WifiManager.TrafficStateCallback)} * * @param callback Traffic State callback to unregister * * @throws SecurityException if the caller does not have permission to register a callback */ @Override public void unregisterTrafficStateCallback(ITrafficStateCallback callback) { enforceNetworkSettingsPermission(); if (isVerboseLoggingEnabled()) { mLog.info("unregisterTrafficStateCallback uid=%").c(Binder.getCallingUid()).flush(); } // Post operation to handler thread mWifiThreadRunner.post(() -> mWifiTrafficPoller.removeCallback(callback)); } private long getSupportedFeaturesInternal() { long supportedFeatureSet = mWifiThreadRunner.call( () -> mActiveModeWarden.getPrimaryClientModeManager().getSupportedFeatures(), 0L); // Mask the feature set against system properties. boolean rttSupported = mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_WIFI_RTT); if (!rttSupported) { // flags filled in by vendor HAL, remove if overlay disables it. supportedFeatureSet &= ~(WifiManager.WIFI_FEATURE_D2D_RTT | WifiManager.WIFI_FEATURE_D2AP_RTT); } if (!mContext.getResources().getBoolean( R.bool.config_wifi_p2p_mac_randomization_supported)) { // flags filled in by vendor HAL, remove if overlay disables it. supportedFeatureSet &= ~WifiManager.WIFI_FEATURE_P2P_RAND_MAC; } if (mContext.getResources().getBoolean( R.bool.config_wifi_connected_mac_randomization_supported)) { // no corresponding flags in vendor HAL, set if overlay enables it. supportedFeatureSet |= WifiManager.WIFI_FEATURE_CONNECTED_RAND_MAC; } if (ApConfigUtil.isApMacRandomizationSupported(mContext)) { // no corresponding flags in vendor HAL, set if overlay enables it. supportedFeatureSet |= WifiManager.WIFI_FEATURE_AP_RAND_MAC; } if (SdkLevel.isAtLeastS()) { if (ApConfigUtil.isBridgedModeSupported(mContext)) { // The bridged mode requires the kernel network modules support. // It doesn't relate the vendor HAL, set if overlay enables it. supportedFeatureSet |= WifiManager.WIFI_FEATURE_BRIDGED_AP; } if (mContext.getResources().getBoolean( R.bool.config_wifiStaWithBridgedSoftApConcurrencySupported)) { // The bridged mode requires the kernel network modules support. // It doesn't relate the vendor HAL, set if overlay enables it. supportedFeatureSet |= WifiManager.WIFI_FEATURE_STA_BRIDGED_AP; } } supportedFeatureSet |= mWifiThreadRunner.call( () -> { long concurrencyFeatureSet = 0L; if (mActiveModeWarden.isStaApConcurrencySupported()) { concurrencyFeatureSet |= WifiManager.WIFI_FEATURE_AP_STA; } if (mActiveModeWarden.isStaStaConcurrencySupportedForLocalOnlyConnections()) { concurrencyFeatureSet |= WifiManager.WIFI_FEATURE_ADDITIONAL_STA_LOCAL_ONLY; } if (mActiveModeWarden.isStaStaConcurrencySupportedForMbb()) { concurrencyFeatureSet |= WifiManager.WIFI_FEATURE_ADDITIONAL_STA_MBB; } if (mActiveModeWarden.isStaStaConcurrencySupportedForRestrictedConnections()) { concurrencyFeatureSet |= WifiManager.WIFI_FEATURE_ADDITIONAL_STA_RESTRICTED; } return concurrencyFeatureSet; }, 0L); return supportedFeatureSet; } private static boolean hasAutomotiveFeature(Context context) { return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); } /** * See * {@link WifiManager#registerNetworkRequestMatchCallback( * Executor, WifiManager.NetworkRequestMatchCallback)} * * @param callback Network Request Match callback to register * * @throws SecurityException if the caller does not have permission to register a callback * @throws RemoteException if remote exception happens * @throws IllegalArgumentException if the arguments are null or invalid */ @Override public void registerNetworkRequestMatchCallback(INetworkRequestMatchCallback callback) { // verify arguments if (callback == null) { throw new IllegalArgumentException("Callback must not be null"); } enforceNetworkSettingsPermission(); if (isVerboseLoggingEnabled()) { mLog.info("registerNetworkRequestMatchCallback uid=%") .c(Binder.getCallingUid()).flush(); } // Post operation to handler thread mWifiThreadRunner.post(() -> mWifiInjector.getWifiNetworkFactory().addCallback(callback)); } /** * see {@link android.net.wifi.WifiManager#unregisterNetworkRequestMatchCallback( * WifiManager.NetworkRequestMatchCallback)} * * @param callback Network Request Match callback to unregister * * @throws SecurityException if the caller does not have permission to register a callback */ @Override public void unregisterNetworkRequestMatchCallback(INetworkRequestMatchCallback callback) { enforceNetworkSettingsPermission(); if (isVerboseLoggingEnabled()) { mLog.info("unregisterNetworkRequestMatchCallback uid=%") .c(Binder.getCallingUid()).flush(); } // Post operation to handler thread mWifiThreadRunner.post(() -> mWifiInjector.getWifiNetworkFactory().removeCallback(callback)); } /** * See {@link android.net.wifi.WifiManager#addNetworkSuggestions(List)} * * @param networkSuggestions List of network suggestions to be added. * @param callingPackageName Package Name of the app adding the suggestions. * @param callingFeatureId Feature in the calling package * @throws SecurityException if the caller does not have permission. * @return One of status codes from {@link WifiManager.NetworkSuggestionsStatusCode}. */ @Override public int addNetworkSuggestions( List networkSuggestions, String callingPackageName, String callingFeatureId) { if (enforceChangePermission(callingPackageName) != MODE_ALLOWED) { return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_APP_DISALLOWED; } if (isVerboseLoggingEnabled()) { mLog.info("addNetworkSuggestions uid=%").c(Binder.getCallingUid()).flush(); } int callingUid = Binder.getCallingUid(); int success = mWifiThreadRunner.call(() -> mWifiNetworkSuggestionsManager.add( networkSuggestions, callingUid, callingPackageName, callingFeatureId), WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL); if (success != WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) { Log.e(TAG, "Failed to add network suggestions"); } return success; } /** * See {@link android.net.wifi.WifiManager#removeNetworkSuggestions(List)} * * @param networkSuggestions List of network suggestions to be removed. * @param callingPackageName Package Name of the app removing the suggestions. * @throws SecurityException if the caller does not have permission. * @return One of status codes from {@link WifiManager.NetworkSuggestionsStatusCode}. */ @Override public int removeNetworkSuggestions( List networkSuggestions, String callingPackageName) { if (enforceChangePermission(callingPackageName) != MODE_ALLOWED) { return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_APP_DISALLOWED; } if (isVerboseLoggingEnabled()) { mLog.info("removeNetworkSuggestions uid=%").c(Binder.getCallingUid()).flush(); } int callingUid = Binder.getCallingUid(); int success = mWifiThreadRunner.call(() -> mWifiNetworkSuggestionsManager.remove( networkSuggestions, callingUid, callingPackageName), WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL); if (success != WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) { Log.e(TAG, "Failed to remove network suggestions"); } return success; } /** * See {@link android.net.wifi.WifiManager#getNetworkSuggestions()} * @param callingPackageName Package Name of the app getting the suggestions. * @return a list of network suggestions suggested by this app */ @Override public List getNetworkSuggestions(String callingPackageName) { int callingUid = Binder.getCallingUid(); mAppOps.checkPackage(callingUid, callingPackageName); enforceAccessPermission(); if (isVerboseLoggingEnabled()) { mLog.info("getNetworkSuggestionList uid=%").c(Binder.getCallingUid()).flush(); } return mWifiThreadRunner.call(() -> mWifiNetworkSuggestionsManager.get(callingPackageName, callingUid), Collections.emptyList()); } /** * Gets the factory Wi-Fi MAC addresses. * @throws SecurityException if the caller does not have permission. * @return Array of String representing Wi-Fi MAC addresses, or empty array if failed. */ @Override public String[] getFactoryMacAddresses() { final int uid = Binder.getCallingUid(); if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) { throw new SecurityException("App not allowed to get Wi-Fi factory MAC address " + "(uid = " + uid + ")"); } String result = mWifiThreadRunner.call( () -> mActiveModeWarden.getPrimaryClientModeManager().getFactoryMacAddress(), null); // result can be empty array if either: WifiThreadRunner.call() timed out, or // ClientModeImpl.getFactoryMacAddress() returned null. // In this particular instance, we don't differentiate the two types of nulls. if (result == null) { return new String[0]; } return new String[]{result}; } /** * Sets the current device mobility state. * @param state the new device mobility state */ @Override public void setDeviceMobilityState(@DeviceMobilityState int state) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE, "WifiService"); if (isVerboseLoggingEnabled()) { mLog.info("setDeviceMobilityState uid=% state=%") .c(Binder.getCallingUid()) .c(state) .flush(); } // Post operation to handler thread mWifiThreadRunner.post(() -> { mWifiConnectivityManager.setDeviceMobilityState(state); mWifiHealthMonitor.setDeviceMobilityState(state); mWifiDataStall.setDeviceMobilityState(state); }); } /** * Proxy for the final native call of the parent class. Enables mocking of * the function. */ public int getMockableCallingUid() { return getCallingUid(); } /** * Start DPP in Configurator-Initiator role. The current device will initiate DPP bootstrapping * with a peer, and send the SSID and password of the selected network. * * @param binder Caller's binder context * @param packageName Package name of the calling app * @param enrolleeUri URI of the Enrollee obtained externally (e.g. QR code scanning) * @param selectedNetworkId Selected network ID to be sent to the peer * @param netRole The network role of the enrollee * @param callback Callback for status updates */ @Override public void startDppAsConfiguratorInitiator(IBinder binder, @NonNull String packageName, String enrolleeUri, int selectedNetworkId, int netRole, IDppCallback callback) { // verify arguments if (binder == null) { throw new IllegalArgumentException("Binder must not be null"); } if (TextUtils.isEmpty(enrolleeUri)) { throw new IllegalArgumentException("Enrollee URI must not be null or empty"); } if (selectedNetworkId < 0) { throw new IllegalArgumentException("Selected network ID invalid"); } if (callback == null) { throw new IllegalArgumentException("Callback must not be null"); } final int uid = getMockableCallingUid(); int callingUid = Binder.getCallingUid(); mAppOps.checkPackage(callingUid, packageName); if (!isSettingsOrSuw(Binder.getCallingPid(), callingUid)) { throw new SecurityException(TAG + ": Permission denied"); } // Stop MBB (if in progress) when DPP is initiated. Otherwise, DPP operation will fail // when the previous primary iface is removed after MBB completion. mWifiThreadRunner.post(() -> mMakeBeforeBreakManager.stopAllSecondaryTransientClientModeManagers(() -> mDppManager.startDppAsConfiguratorInitiator( uid, packageName, mActiveModeWarden.getPrimaryClientModeManager().getInterfaceName(), binder, enrolleeUri, removeSecurityTypeFromNetworkId(selectedNetworkId), netRole, callback))); } /** * Start DPP in Enrollee-Initiator role. The current device will initiate DPP bootstrapping * with a peer, and receive the SSID and password from the peer configurator. * * @param binder Caller's binder context * @param configuratorUri URI of the Configurator obtained externally (e.g. QR code scanning) * @param callback Callback for status updates */ @Override public void startDppAsEnrolleeInitiator(IBinder binder, String configuratorUri, IDppCallback callback) { // verify arguments if (binder == null) { throw new IllegalArgumentException("Binder must not be null"); } if (TextUtils.isEmpty(configuratorUri)) { throw new IllegalArgumentException("Enrollee URI must not be null or empty"); } if (callback == null) { throw new IllegalArgumentException("Callback must not be null"); } final int uid = getMockableCallingUid(); if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) { throw new SecurityException(TAG + ": Permission denied"); } // Stop MBB (if in progress) when DPP is initiated. Otherwise, DPP operation will fail // when the previous primary iface is removed after MBB completion. mWifiThreadRunner.post(() -> mMakeBeforeBreakManager.stopAllSecondaryTransientClientModeManagers(() -> mDppManager.startDppAsEnrolleeInitiator(uid, mActiveModeWarden.getPrimaryClientModeManager().getInterfaceName(), binder, configuratorUri, callback))); } /** * Start DPP in Enrollee-Responder role. The current device will generate the * bootstrap code and wait for the peer device to start the DPP authentication process. * * @param binder Caller's binder context * @param deviceInfo Device specific info to display in QR code(e.g. Easy_connect_demo) * @param curve Elliptic curve cryptography type used to generate DPP public/private key pair. * @param callback Callback for status updates */ @Override @RequiresApi(Build.VERSION_CODES.S) public void startDppAsEnrolleeResponder(IBinder binder, @Nullable String deviceInfo, @WifiManager.EasyConnectCryptographyCurve int curve, IDppCallback callback) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } // verify arguments if (binder == null) { throw new IllegalArgumentException("Binder must not be null"); } if (callback == null) { throw new IllegalArgumentException("Callback must not be null"); } final int uid = getMockableCallingUid(); if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) { throw new SecurityException(TAG + ": Permission denied"); } if (deviceInfo != null) { int deviceInfoLen = deviceInfo.length(); if (deviceInfoLen > WifiManager.getEasyConnectMaxAllowedResponderDeviceInfoLength()) { throw new IllegalArgumentException("Device info length: " + deviceInfoLen + " must be less than " + WifiManager.getEasyConnectMaxAllowedResponderDeviceInfoLength()); } char c; for (int i = 0; i < deviceInfoLen; i++) { c = deviceInfo.charAt(i); if (c < '!' || c > '~' || c == ';') { throw new IllegalArgumentException("Allowed Range of ASCII characters in" + "deviceInfo - %x20-7E; semicolon and space are not allowed!" + "Found c: " + c); } } } // Stop MBB (if in progress) when DPP is initiated. Otherwise, DPP operation will fail // when the previous primary iface is removed after MBB completion. mWifiThreadRunner.post(() -> mMakeBeforeBreakManager.stopAllSecondaryTransientClientModeManagers(() -> mDppManager.startDppAsEnrolleeResponder(uid, mActiveModeWarden.getPrimaryClientModeManager().getInterfaceName(), binder, deviceInfo, curve, callback))); } /** * Stop or abort a current DPP session. */ @Override public void stopDppSession() throws RemoteException { if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) { throw new SecurityException(TAG + ": Permission denied"); } final int uid = getMockableCallingUid(); mWifiThreadRunner.post(() -> mDppManager.stopDppSession(uid)); } /** * see {@link android.net.wifi.WifiManager#addWifiVerboseLoggingStatusChangedListener(Executor, * WifiManager.WifiVerboseLoggingStatusChangedListener)} * * @param listener IWifiVerboseLoggingStatusChangedListener listener to add * * @throws SecurityException if the caller does not have permission to add a listener. * @throws IllegalArgumentException if the argument is null. */ @Override public void addWifiVerboseLoggingStatusChangedListener( IWifiVerboseLoggingStatusChangedListener listener) { if (listener == null) { throw new IllegalArgumentException("Listener must not be null"); } enforceAccessPermission(); // Post operation to handler thread mWifiThreadRunner.post(() -> mRegisteredWifiLoggingStatusListeners.register(listener)); } /** * see {@link android.net.wifi.WifiManager#unregisterWifiVerboseLoggingStatusCallback * (WifiManager.WifiVerboseLoggingStatusCallback)} * * @param listener the listener to be removed. * * @throws SecurityException if the caller does not have permission to add a listener. * @throws IllegalArgumentException if the argument is null. */ @Override public void removeWifiVerboseLoggingStatusChangedListener( IWifiVerboseLoggingStatusChangedListener listener) { if (listener == null) { throw new IllegalArgumentException("Listener must not be null"); } enforceAccessPermission(); // Post operation to handler thread mWifiThreadRunner.post(() -> mRegisteredWifiLoggingStatusListeners.unregister(listener)); } /** * see {@link android.net.wifi.WifiManager#addOnWifiUsabilityStatsListener(Executor, * WifiManager.OnWifiUsabilityStatsListener)} * * @param listener WifiUsabilityStatsEntry listener to add * * @throws SecurityException if the caller does not have permission to add a listener * @throws RemoteException if remote exception happens * @throws IllegalArgumentException if the arguments are null or invalid */ @Override public void addOnWifiUsabilityStatsListener(IOnWifiUsabilityStatsListener listener) { if (listener == null) { throw new IllegalArgumentException("Listener must not be null"); } mContext.enforceCallingOrSelfPermission( android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE, "WifiService"); if (isVerboseLoggingEnabled()) { mLog.info("addOnWifiUsabilityStatsListener uid=%") .c(Binder.getCallingUid()).flush(); } // Post operation to handler thread mWifiThreadRunner.post(() -> mWifiMetrics.addOnWifiUsabilityListener(listener)); } /** * see {@link android.net.wifi.WifiManager#removeOnWifiUsabilityStatsListener * (WifiManager.OnWifiUsabilityStatsListener)} * * @param listener listener to be removed. * * @throws SecurityException if the caller does not have permission to add a listener */ @Override public void removeOnWifiUsabilityStatsListener(IOnWifiUsabilityStatsListener listener) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE, "WifiService"); if (isVerboseLoggingEnabled()) { mLog.info("removeOnWifiUsabilityStatsListener uid=%") .c(Binder.getCallingUid()).flush(); } // Post operation to handler thread mWifiThreadRunner.post(() -> mWifiMetrics.removeOnWifiUsabilityListener(listener)); } /** * Updates the Wi-Fi usability score. * @param seqNum Sequence number of the Wi-Fi usability score. * @param score The Wi-Fi usability score. * @param predictionHorizonSec Prediction horizon of the Wi-Fi usability score in second. */ @Override public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE, "WifiService"); if (isVerboseLoggingEnabled()) { mLog.info("updateWifiUsabilityScore uid=% seqNum=% score=% predictionHorizonSec=%") .c(Binder.getCallingUid()) .c(seqNum) .c(score) .c(predictionHorizonSec) .flush(); } // Post operation to handler thread mWifiThreadRunner.post(() -> { String ifaceName = mActiveModeWarden.getPrimaryClientModeManager().getInterfaceName(); mWifiMetrics.incrementWifiUsabilityScoreCount( ifaceName, seqNum, score, predictionHorizonSec); }); } /** * Notify interested parties if a wifi config has been changed. * * @param wifiCredentialEventType WIFI_CREDENTIAL_SAVED or WIFI_CREDENTIAL_FORGOT * @param config Must have a WifiConfiguration object to succeed */ private void broadcastWifiCredentialChanged(int wifiCredentialEventType, WifiConfiguration config) { Intent intent = new Intent(WifiManager.WIFI_CREDENTIAL_CHANGED_ACTION); if (config != null && config.SSID != null && mWifiPermissionsUtil.isLocationModeEnabled()) { intent.putExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_SSID, config.SSID); } intent.putExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_EVENT_TYPE, wifiCredentialEventType); mContext.createContextAsUser(UserHandle.CURRENT, 0) .sendBroadcastWithMultiplePermissions( intent, new String[]{ android.Manifest.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE, android.Manifest.permission.ACCESS_FINE_LOCATION, }); } /** * Connects to a network. * * If the supplied config is not null, then the netId argument will be ignored and the config * will be saved (or updated if its networkId or profile key already exist) and connected to. * * If the supplied config is null, then the netId argument will be matched to a saved config to * be connected to. * * @param config New or existing config to add/update and connect to * @param netId Network ID of existing config to connect to if the supplied config is null * @param callback Listener to notify action result * * see: {@link WifiManager#connect(WifiConfiguration, WifiManager.ActionListener)} * {@link WifiManager#connect(int, WifiManager.ActionListener)} */ @Override public void connect(WifiConfiguration config, int netId, @Nullable IActionListener callback) { int uid = Binder.getCallingUid(); if (!isPrivileged(Binder.getCallingPid(), uid)) { throw new SecurityException(TAG + ": Permission denied"); } if (config != null) { config.networkId = removeSecurityTypeFromNetworkId(config.networkId); } final int netIdArg = removeSecurityTypeFromNetworkId(netId); mLog.info("connect uid=%").c(uid).flush(); mWifiThreadRunner.post(() -> { ActionListenerWrapper wrapper = new ActionListenerWrapper(callback); final NetworkUpdateResult result; // if connecting using WifiConfiguration, save the network first if (config != null) { if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) { mWifiMetrics.logUserActionEvent( UserActionEvent.EVENT_ADD_OR_UPDATE_NETWORK, config.networkId); } result = mWifiConfigManager.addOrUpdateNetwork(config, uid); if (!result.isSuccess()) { Log.e(TAG, "connect adding/updating config=" + config + " failed"); wrapper.sendFailure(WifiManager.ERROR); return; } broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config); } else { if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) { mWifiMetrics.logUserActionEvent( UserActionEvent.EVENT_MANUAL_CONNECT, netIdArg); } result = new NetworkUpdateResult(netIdArg); } WifiConfiguration configuration = mWifiConfigManager .getConfiguredNetwork(result.getNetworkId()); if (configuration == null) { Log.e(TAG, "connect to Invalid network Id=" + netIdArg); wrapper.sendFailure(WifiManager.ERROR); return; } if (configuration.enterpriseConfig != null && configuration.enterpriseConfig.isAuthenticationSimBased()) { int subId = mWifiCarrierInfoManager.getBestMatchSubscriptionId(configuration); if (!mWifiCarrierInfoManager.isSimReady(subId)) { Log.e(TAG, "connect to SIM-based config=" + configuration + "while SIM is absent"); wrapper.sendFailure(WifiManager.ERROR); return; } if (mWifiCarrierInfoManager.requiresImsiEncryption(subId) && !mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(subId)) { Log.e(TAG, "Imsi protection required but not available for Network=" + configuration); wrapper.sendFailure(WifiManager.ERROR); return; } } mMakeBeforeBreakManager.stopAllSecondaryTransientClientModeManagers(() -> mConnectHelper.connectToNetwork(result, wrapper, uid)); }); } /** * see {@link android.net.wifi.WifiManager#save(WifiConfiguration, * WifiManager.ActionListener)} */ @Override public void save(WifiConfiguration config, @Nullable IActionListener callback) { int uid = Binder.getCallingUid(); if (!isPrivileged(Binder.getCallingPid(), uid)) { throw new SecurityException(TAG + ": Permission denied"); } if (config != null) { config.networkId = removeSecurityTypeFromNetworkId(config.networkId); } mLog.info("save uid=%").c(uid).flush(); mWifiThreadRunner.post(() -> { ActionListenerWrapper wrapper = new ActionListenerWrapper(callback); NetworkUpdateResult result = mWifiConfigManager.updateBeforeSaveNetwork(config, uid); if (result.isSuccess()) { broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config); mMakeBeforeBreakManager.stopAllSecondaryTransientClientModeManagers(() -> mActiveModeWarden.getPrimaryClientModeManager() .saveNetwork(result, wrapper, uid)); if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) { mWifiMetrics.logUserActionEvent( UserActionEvent.EVENT_ADD_OR_UPDATE_NETWORK, config.networkId); } } else { wrapper.sendFailure(WifiManager.ERROR); } }); } /** * see {@link android.net.wifi.WifiManager#forget(int, WifiManager.ActionListener)} */ @Override public void forget(int netId, @Nullable IActionListener callback) { int uid = Binder.getCallingUid(); if (!isPrivileged(Binder.getCallingPid(), uid)) { throw new SecurityException(TAG + ": Permission denied"); } final int internalNetId = removeSecurityTypeFromNetworkId(netId); mLog.info("forget uid=%").c(Binder.getCallingUid()).flush(); if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) { // It's important to log this metric before the actual forget executes because // the netId becomes invalid after the forget operation. mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_FORGET_WIFI, internalNetId); } mWifiThreadRunner.post(() -> { WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(internalNetId); boolean success = mWifiConfigManager.removeNetwork(internalNetId, uid, null); ActionListenerWrapper wrapper = new ActionListenerWrapper(callback); if (success) { wrapper.sendSuccess(); broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_FORGOT, config); } else { Log.e(TAG, "Failed to remove network"); wrapper.sendFailure(WifiManager.ERROR); } }); } /** * See {@link WifiManager#registerScanResultsCallback(WifiManager.ScanResultsCallback)} */ public void registerScanResultsCallback(@NonNull IScanResultsCallback callback) { if (callback == null) { throw new IllegalArgumentException("callback must not be null"); } enforceAccessPermission(); if (isVerboseLoggingEnabled()) { mLog.info("registerScanResultsCallback uid=%").c(Binder.getCallingUid()).flush(); } mWifiThreadRunner.post(() -> { if (!mWifiInjector.getScanRequestProxy().registerScanResultsCallback(callback)) { Log.e(TAG, "registerScanResultsCallback: Failed to register callback"); } }); } /** * See {@link WifiManager#registerScanResultsCallback(WifiManager.ScanResultsCallback)} */ public void unregisterScanResultsCallback(@NonNull IScanResultsCallback callback) { if (isVerboseLoggingEnabled()) { mLog.info("unregisterScanResultCallback uid=%").c(Binder.getCallingUid()).flush(); } enforceAccessPermission(); // post operation to handler thread mWifiThreadRunner.post(() -> mWifiInjector.getScanRequestProxy() .unregisterScanResultsCallback(callback)); } /** * See {@link WifiManager#addSuggestionConnectionStatusListener(Executor, * SuggestionConnectionStatusListener)} */ public void registerSuggestionConnectionStatusListener( @NonNull ISuggestionConnectionStatusListener listener, String packageName, @Nullable String featureId) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } final int uid = Binder.getCallingUid(); mWifiPermissionsUtil.checkPackage(uid, packageName); enforceAccessPermission(); enforceLocationPermission(packageName, featureId, uid); if (isVerboseLoggingEnabled()) { mLog.info("registerSuggestionConnectionStatusListener uid=%").c(uid).flush(); } mWifiThreadRunner.post(() -> mWifiNetworkSuggestionsManager .registerSuggestionConnectionStatusListener(listener, packageName, uid)); } /** * See {@link WifiManager#removeSuggestionConnectionStatusListener( * SuggestionConnectionStatusListener)} */ public void unregisterSuggestionConnectionStatusListener( @NonNull ISuggestionConnectionStatusListener listener, String packageName) { enforceAccessPermission(); int uid = Binder.getCallingUid(); if (isVerboseLoggingEnabled()) { mLog.info("unregisterSuggestionConnectionStatusListener uid=%") .c(uid).flush(); } mWifiThreadRunner.post(() -> mWifiNetworkSuggestionsManager .unregisterSuggestionConnectionStatusListener(listener, packageName, uid)); } @Override public int calculateSignalLevel(int rssi) { return RssiUtil.calculateSignalLevel(mContext, rssi); } /** * See {@link android.net.wifi.WifiManager#setWifiConnectedNetworkScorer(Executor, * WifiManager.WifiConnectedNetworkScorer)} * * @param binder IBinder instance to allow cleanup if the app dies. * @param scorer Wifi connected network scorer to set. * @return true Scorer is set successfully. * * @throws RemoteException if remote exception happens * @throws IllegalArgumentException if the arguments are null or invalid */ @Override public boolean setWifiConnectedNetworkScorer(IBinder binder, IWifiConnectedNetworkScorer scorer) { if (binder == null) { throw new IllegalArgumentException("Binder must not be null"); } if (scorer == null) { throw new IllegalArgumentException("Scorer must not be null"); } mContext.enforceCallingOrSelfPermission( android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE, "WifiService"); if (isVerboseLoggingEnabled()) { mLog.info("setWifiConnectedNetworkScorer uid=%").c(Binder.getCallingUid()).flush(); } // Post operation to handler thread return mWifiThreadRunner.call( () -> mActiveModeWarden.setWifiConnectedNetworkScorer(binder, scorer), false); } /** * See {@link WifiManager#clearWifiConnectedNetworkScorer()} */ @Override public void clearWifiConnectedNetworkScorer() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE, "WifiService"); if (isVerboseLoggingEnabled()) { mLog.info("clearWifiConnectedNetworkScorer uid=%").c(Binder.getCallingUid()).flush(); } // Post operation to handler thread mWifiThreadRunner.post(() -> mActiveModeWarden.clearWifiConnectedNetworkScorer()); } /** * See {@link android.net.wifi.WifiManager#setScanThrottleEnabled(boolean)} */ @Override public void setScanThrottleEnabled(boolean enable) { enforceNetworkSettingsPermission(); mLog.info("setScanThrottleEnabled uid=% verbose=%") .c(Binder.getCallingUid()) .c(enable).flush(); mWifiThreadRunner.post(()-> mScanRequestProxy.setScanThrottleEnabled(enable)); } /** * See {@link android.net.wifi.WifiManager#isScanThrottleEnabled()} */ @Override public boolean isScanThrottleEnabled() { enforceAccessPermission(); if (isVerboseLoggingEnabled()) { mLog.info("isScanThrottleEnabled uid=%").c(Binder.getCallingUid()).flush(); } return mWifiThreadRunner.call(()-> mScanRequestProxy.isScanThrottleEnabled(), true); } /** * See {@link android.net.wifi.WifiManager#setAutoWakeupEnabled(boolean)} */ @Override public void setAutoWakeupEnabled(boolean enable) { enforceNetworkSettingsPermission(); mLog.info("setWalkeupEnabled uid=% verbose=%") .c(Binder.getCallingUid()) .c(enable).flush(); mWifiThreadRunner.post(()-> mWifiInjector.getWakeupController().setEnabled(enable)); } /** * See {@link android.net.wifi.WifiManager#isAutoWakeupEnabled()} */ @Override public boolean isAutoWakeupEnabled() { enforceAccessPermission(); if (isVerboseLoggingEnabled()) { mLog.info("isAutoWakeupEnabled uid=%").c(Binder.getCallingUid()).flush(); } return mWifiThreadRunner.call(()-> mWifiInjector.getWakeupController().isEnabled(), false); } /** * See {@link android.net.wifi.WifiManager#setCarrierNetworkOffloadEnabled(int, boolean, boolean)} */ @Override public void setCarrierNetworkOffloadEnabled(int subscriptionId, boolean merged, boolean enabled) { if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) { throw new SecurityException(TAG + ": Permission denied"); } if (isVerboseLoggingEnabled()) { mLog.info("setCarrierNetworkOffloadEnabled uid=%").c(Binder.getCallingUid()).flush(); } mWifiThreadRunner.post(() -> mWifiCarrierInfoManager.setCarrierNetworkOffloadEnabled(subscriptionId, merged, enabled)); } /** * See {@link android.net.wifi.WifiManager#isCarrierNetworkOffloadEnabled(int, boolean)} */ @Override public boolean isCarrierNetworkOffloadEnabled(int subId, boolean merged) { enforceAccessPermission(); if (isVerboseLoggingEnabled()) { mLog.info("isCarrierNetworkOffload uid=%").c(Binder.getCallingUid()).flush(); } return mWifiThreadRunner.call(()-> mWifiCarrierInfoManager.isCarrierNetworkOffloadEnabled(subId, merged), true); } /** * See {@link android.net.wifi.WifiManager#addSuggestionUserApprovalStatusListener(Executor, * WifiManager.SuggestionUserApprovalStatusListener)} */ @Override public void addSuggestionUserApprovalStatusListener( ISuggestionUserApprovalStatusListener listener, String packageName) { if (listener == null) { throw new NullPointerException("listener must not be null"); } final int uid = Binder.getCallingUid(); enforceAccessPermission(); long callingIdentity = Binder.clearCallingIdentity(); try { if (!mWifiPermissionsUtil.doesUidBelongToCurrentUser(uid)) { Log.e(TAG, "UID " + uid + " not visible to the current user"); throw new SecurityException("UID " + uid + " not visible to the current user"); } } finally { // restore calling identity Binder.restoreCallingIdentity(callingIdentity); } if (isVerboseLoggingEnabled()) { mLog.info("addSuggestionUserApprovalStatusListener uid=%").c(uid).flush(); } mWifiThreadRunner.post(() -> mWifiNetworkSuggestionsManager .addSuggestionUserApprovalStatusListener(listener, packageName, uid)); } /** * See {@link android.net.wifi.WifiManager#removeSuggestionUserApprovalStatusListener( * WifiManager.SuggestionUserApprovalStatusListener)} */ @Override public void removeSuggestionUserApprovalStatusListener( ISuggestionUserApprovalStatusListener listener, String packageName) { enforceAccessPermission(); int uid = Binder.getCallingUid(); long callingIdentity = Binder.clearCallingIdentity(); try { if (!mWifiPermissionsUtil.doesUidBelongToCurrentUser(uid)) { Log.e(TAG, "UID " + uid + " not visible to the current user"); throw new SecurityException("UID " + uid + " not visible to the current user"); } } finally { // restore calling identity Binder.restoreCallingIdentity(callingIdentity); } if (isVerboseLoggingEnabled()) { mLog.info("removeSuggestionUserApprovalStatusListener uid=%") .c(uid).flush(); } mWifiThreadRunner.post(() -> mWifiNetworkSuggestionsManager .removeSuggestionUserApprovalStatusListener(listener, packageName, uid)); } /** * See {@link android.net.wifi.WifiManager#setEmergencyScanRequestInProgress(boolean)}. */ @Override public void setEmergencyScanRequestInProgress(boolean inProgress) { enforceNetworkStackPermission(); int uid = Binder.getCallingUid(); mLog.info("setEmergencyScanRequestInProgress uid=%").c(uid).flush(); mActiveModeWarden.setEmergencyScanRequestInProgress(inProgress); } /** * See {@link android.net.wifi.WifiManager#removeAppState(int, String)}. */ @Override public void removeAppState(int targetAppUid, @NonNull String targetAppPackageName) { enforceNetworkSettingsPermission(); mLog.info("removeAppState uid=%").c(Binder.getCallingUid()).flush(); mWifiThreadRunner.post(() -> { removeAppStateInternal(targetAppUid, targetAppPackageName); }); } /** * See {@link android.net.wifi.WifiManager#setWifiScoringEnabled(boolean)}. */ @Override public boolean setWifiScoringEnabled(boolean enabled) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.NETWORK_SETTINGS, "WifiService"); // Post operation to handler thread return mWifiThreadRunner.call( () -> mSettingsStore.handleWifiScoringEnabled(enabled), false); } @VisibleForTesting static boolean isValidBandForGetUsableChannels(@WifiScanner.WifiBand int band) { switch (band) { case WifiScanner.WIFI_BAND_UNSPECIFIED: case WifiScanner.WIFI_BAND_24_GHZ: case WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS: case WifiScanner.WIFI_BAND_BOTH_WITH_DFS: case WifiScanner.WIFI_BAND_6_GHZ: case WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_GHZ: case WifiScanner.WIFI_BAND_60_GHZ: case WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ: return true; default: return false; } } /** * See {@link android.net.wifi.WifiManager#getUsableChannels(int, int) and * See {@link android.net.wifi.WifiManager#getAllowedChannels(int, int). * * @throws SecurityException if the caller does not have permission * or IllegalArgumentException if the band is invalid for this method. */ @Override public List getUsableChannels(@WifiScanner.WifiBand int band, @WifiAvailableChannel.OpMode int mode, @WifiAvailableChannel.Filter int filter) { // Location mode must be enabled if (!mWifiPermissionsUtil.isLocationModeEnabled()) { throw new SecurityException("Location mode is disabled for the device"); } final int uid = Binder.getCallingUid(); if (isVerboseLoggingEnabled()) { mLog.info("getUsableChannels uid=%").c(Binder.getCallingUid()).flush(); } if (!mWifiPermissionsUtil.checkCallersHardwareLocationPermission(uid)) { throw new SecurityException("UID " + uid + " does not have location h/w permission"); } if (!isValidBandForGetUsableChannels(band)) { throw new IllegalArgumentException("Unsupported band: " + band); } List channels = mWifiThreadRunner.call( () -> mWifiNative.getUsableChannels(band, mode, filter), null); if (channels == null) { throw new UnsupportedOperationException(); } return channels; } private void resetNotificationManager() { mWifiInjector.getWifiNotificationManager().createNotificationChannels(); mWifiInjector.getOpenNetworkNotifier().clearPendingNotification(false); mWifiCarrierInfoManager.resetNotification(); mWifiNetworkSuggestionsManager.resetNotification(); mWifiInjector.getWakeupController().resetNotification(); } /** * See {@link android.net.wifi.WifiManager#flushPasspointAnqpCache()}. */ @Override public void flushPasspointAnqpCache(@NonNull String packageName) { mWifiPermissionsUtil.checkPackage(Binder.getCallingUid(), packageName); if (!isDeviceOrProfileOwner(Binder.getCallingUid(), packageName)) { enforceAnyPermissionOf(android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_MANAGED_PROVISIONING, android.Manifest.permission.NETWORK_CARRIER_PROVISIONING); } mWifiThreadRunner.post(mPasspointManager::clearAnqpRequestsAndFlushCache); } }