summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--service/java/com/android/server/wifi/ClientModeImpl.java14
-rw-r--r--service/java/com/android/server/wifi/ConnectionFailureNotificationBuilder.java123
-rw-r--r--service/java/com/android/server/wifi/ConnectionFailureNotifier.java162
-rw-r--r--service/java/com/android/server/wifi/FrameworkFacade.java21
-rw-r--r--service/java/com/android/server/wifi/WifiInjector.java27
-rw-r--r--tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java42
-rw-r--r--tests/wifitests/src/com/android/server/wifi/ConnectionFailureNotifierTest.java184
7 files changed, 571 insertions, 2 deletions
diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java
index 119d6d0e8..5cf909695 100644
--- a/service/java/com/android/server/wifi/ClientModeImpl.java
+++ b/service/java/com/android/server/wifi/ClientModeImpl.java
@@ -763,6 +763,7 @@ public class ClientModeImpl extends StateMachine {
private WifiStateTracker mWifiStateTracker;
private final BackupManagerProxy mBackupManagerProxy;
private final WrongPasswordNotifier mWrongPasswordNotifier;
+ private final ConnectionFailureNotifier mConnectionFailureNotifier;
private WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
private boolean mConnectedMacRandomzationSupported;
// Maximum duration to continue to log Wifi usability stats after a data stall is triggered.
@@ -816,7 +817,8 @@ public class ClientModeImpl extends StateMachine {
mSupplicantStateTracker =
mFacade.makeSupplicantStateTracker(context, mWifiConfigManager, getHandler());
mWifiConnectivityManager = mWifiInjector.makeWifiConnectivityManager(this);
-
+ mConnectionFailureNotifier = mWifiInjector.makeConnectionFailureNotifier(
+ mWifiConnectivityManager);
mLinkProperties = new LinkProperties();
mMcastLockManagerFilterController = new McastLockManagerFilterController();
@@ -3158,6 +3160,16 @@ public class ClientModeImpl extends StateMachine {
mWifiScoreCard.noteConnectionFailure(mWifiInfo,
level2FailureCode, connectivityFailureCode);
}
+ boolean isAssociationRejection = level2FailureCode
+ == WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION;
+ boolean isAuthenticationFailure = level2FailureCode
+ == WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE
+ && level2FailureReason != WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD;
+ if ((isAssociationRejection || isAuthenticationFailure)
+ && mWifiConfigManager.isInFlakyRandomizationSsidHotlist(mTargetNetworkId)) {
+ mConnectionFailureNotifier
+ .showFailedToConnectDueToNoRandomizedMacSupportNotification(mTargetNetworkId);
+ }
// if connected, this should be non-null.
WifiConfiguration configuration = getCurrentWifiConfiguration();
if (configuration == null) {
diff --git a/service/java/com/android/server/wifi/ConnectionFailureNotificationBuilder.java b/service/java/com/android/server/wifi/ConnectionFailureNotificationBuilder.java
new file mode 100644
index 000000000..f4f89f09e
--- /dev/null
+++ b/service/java/com/android/server/wifi/ConnectionFailureNotificationBuilder.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.annotation.NonNull;
+import android.app.AlertDialog;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.wifi.WifiConfiguration;
+import android.os.Handler;
+import android.view.WindowManager;
+
+import com.android.internal.R;
+import com.android.internal.notification.SystemNotificationChannels;
+
+/**
+ * Helper class for ConnectionFailureNotifier.
+ */
+public class ConnectionFailureNotificationBuilder {
+ private static final String TAG = "ConnectionFailureNotifier";
+
+ public static final String ACTION_SHOW_SET_RANDOMIZATION_DETAILS =
+ "com.android.server.wifi.ACTION_SHOW_SET_RANDOMIZATION_DETAILS";
+ public static final String RANDOMIZATION_SETTINGS_NETWORK_ID =
+ "com.android.server.wifi.RANDOMIZATION_SETTINGS_NETWORK_ID";
+ public static final String RANDOMIZATION_SETTINGS_NETWORK_SSID =
+ "com.android.server.wifi.RANDOMIZATION_SETTINGS_NETWORK_SSID";
+
+ private Context mContext;
+ private String mPackageName;
+ private Resources mResources;
+ private FrameworkFacade mFrameworkFacade;
+ private WifiConnectivityManager mWifiConnectivityManager;
+ private NotificationManager mNotificationManager;
+ private Handler mHandler;
+
+ public ConnectionFailureNotificationBuilder(Context context, String packageName,
+ FrameworkFacade framework) {
+ mContext = context;
+ mPackageName = packageName;
+ mResources = context.getResources();
+ mFrameworkFacade = framework;
+ }
+
+ /**
+ * Creates a notification that alerts the user that the connection may be failing due to
+ * MAC randomization.
+ * @param config
+ */
+ public Notification buildNoMacRandomizationSupportNotification(
+ @NonNull WifiConfiguration config) {
+ String ssid = config.SSID;
+ String ssidAndSecurityType = config.getSsidAndSecurityTypeString();
+ String title = mResources.getString(
+ R.string.wifi_cannot_connect_with_randomized_mac_title, ssid);
+ String content = mResources.getString(
+ R.string.wifi_cannot_connect_with_randomized_mac_message);
+
+ Intent showDetailIntent = new Intent(ACTION_SHOW_SET_RANDOMIZATION_DETAILS)
+ .setPackage(mPackageName);
+ showDetailIntent.putExtra(RANDOMIZATION_SETTINGS_NETWORK_ID, config.networkId);
+ showDetailIntent.putExtra(RANDOMIZATION_SETTINGS_NETWORK_SSID, ssidAndSecurityType);
+ PendingIntent pendingShowDetailIntent = mFrameworkFacade.getBroadcast(
+ mContext, 0, showDetailIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ return mFrameworkFacade.makeNotificationBuilder(mContext,
+ SystemNotificationChannels.NETWORK_ALERTS)
+ .setSmallIcon(R.drawable.stat_notify_wifi_in_range)
+ .setTicker(title)
+ .setContentTitle(title)
+ .setContentText(content)
+ .setContentIntent(pendingShowDetailIntent)
+ .setShowWhen(false)
+ .setLocalOnly(true)
+ .setColor(mResources.getColor(R.color.system_notification_accent_color,
+ mContext.getTheme()))
+ .setAutoCancel(true)
+ .build();
+ }
+
+ /**
+ * Creates an AlertDialog that allows the user to disable MAC randomization for a network.
+ * @param ssid the displayed SSID in the dialog
+ * @param onUserConfirm
+ */
+ public AlertDialog buildChangeMacRandomizationSettingDialog(
+ String ssid, DialogInterface.OnClickListener onUserConfirm) {
+ AlertDialog.Builder builder = mFrameworkFacade.makeAlertDialogBuilder(mContext)
+ .setTitle(mResources.getString(
+ R.string.wifi_disable_mac_randomization_dialog_title))
+ .setMessage(mResources.getString(
+ R.string.wifi_disable_mac_randomization_dialog_message, ssid))
+ .setPositiveButton(
+ mResources.getString(
+ R.string.wifi_disable_mac_randomization_dialog_confirm_text),
+ onUserConfirm)
+ // A null listener allows the dialog to be dismissed directly.
+ .setNegativeButton(R.string.no, null);
+ AlertDialog dialog = builder.create();
+ dialog.setCanceledOnTouchOutside(false);
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ return dialog;
+ }
+}
diff --git a/service/java/com/android/server/wifi/ConnectionFailureNotifier.java b/service/java/com/android/server/wifi/ConnectionFailureNotifier.java
new file mode 100644
index 000000000..bbef2ff6e
--- /dev/null
+++ b/service/java/com/android/server/wifi/ConnectionFailureNotifier.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.app.AlertDialog;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.net.wifi.WifiConfiguration;
+import android.os.Handler;
+import android.os.Process;
+import android.util.Log;
+
+import com.android.internal.R;
+
+/**
+ * This class may be used to launch notifications when wifi connections fail.
+ */
+public class ConnectionFailureNotifier {
+ private static final String TAG = "ConnectionFailureNotifier";
+ public static final int NO_RANDOMIZED_MAC_SUPPORT_NOTIFICATION_ID = 123;
+
+ private Context mContext;
+ private WifiInjector mWifiInjector;
+ private Resources mResources;
+ private FrameworkFacade mFrameworkFacade;
+ private WifiConfigManager mWifiConfigManager;
+ private WifiConnectivityManager mWifiConnectivityManager;
+ private NotificationManager mNotificationManager;
+ private Handler mHandler;
+ private ConnectionFailureNotificationBuilder mConnectionFailureNotificationBuilder;
+
+ public ConnectionFailureNotifier(
+ Context context, WifiInjector wifiInjector, FrameworkFacade framework,
+ WifiConfigManager wifiConfigManager, WifiConnectivityManager wifiConnectivityManager,
+ Handler handler) {
+ mContext = context;
+ mWifiInjector = wifiInjector;
+ mResources = context.getResources();
+ mFrameworkFacade = framework;
+ mWifiConfigManager = wifiConfigManager;
+ mWifiConnectivityManager = wifiConnectivityManager;
+ mNotificationManager = mWifiInjector.getNotificationManager();
+ mHandler = handler;
+ mConnectionFailureNotificationBuilder =
+ mWifiInjector.getConnectionFailureNotificationBuilder();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ConnectionFailureNotificationBuilder
+ .ACTION_SHOW_SET_RANDOMIZATION_DETAILS);
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(ConnectionFailureNotificationBuilder
+ .ACTION_SHOW_SET_RANDOMIZATION_DETAILS)) {
+ int networkId = intent.getIntExtra(
+ ConnectionFailureNotificationBuilder
+ .RANDOMIZATION_SETTINGS_NETWORK_ID,
+ WifiConfiguration.INVALID_NETWORK_ID);
+ String ssidAndSecurityType = intent.getStringExtra(
+ ConnectionFailureNotificationBuilder
+ .RANDOMIZATION_SETTINGS_NETWORK_SSID);
+ showRandomizationSettingsDialog(networkId, ssidAndSecurityType);
+ }
+ }
+ }, filter);
+ }
+
+ /**
+ * Shows a notification which will bring up a dialog which offers the user an option to disable
+ * MAC randomization on |networkdId|.
+ * @param networkId
+ */
+ public void showFailedToConnectDueToNoRandomizedMacSupportNotification(int networkId) {
+ WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(networkId);
+ if (config == null) {
+ return;
+ }
+ Notification notification = mConnectionFailureNotificationBuilder
+ .buildNoMacRandomizationSupportNotification(config);
+ mNotificationManager.notify(NO_RANDOMIZED_MAC_SUPPORT_NOTIFICATION_ID, notification);
+ }
+
+ class DisableMacRandomizationListener implements DialogInterface.OnClickListener {
+ private WifiConfiguration mConfig;
+
+ DisableMacRandomizationListener(WifiConfiguration config) {
+ mConfig = config;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mHandler.post(() -> {
+ mConfig.macRandomizationSetting =
+ WifiConfiguration.RANDOMIZATION_NONE;
+ mWifiConfigManager.addOrUpdateNetwork(mConfig, Process.SYSTEM_UID);
+ WifiConfiguration updatedConfig =
+ mWifiConfigManager.getConfiguredNetwork(mConfig.networkId);
+ if (updatedConfig.macRandomizationSetting
+ == WifiConfiguration.RANDOMIZATION_NONE) {
+ String message = mResources.getString(
+ R.string.wifi_disable_mac_randomization_dialog_success);
+ mFrameworkFacade.showToast(mContext, message);
+ mWifiConfigManager.enableNetwork(updatedConfig.networkId, true,
+ Process.SYSTEM_UID);
+ mWifiConnectivityManager.forceConnectivityScan(
+ ClientModeImpl.WIFI_WORK_SOURCE);
+ } else {
+ // Shouldn't ever fail, but here for completeness
+ String message = mResources.getString(
+ R.string.wifi_disable_mac_randomization_dialog_failure);
+ mFrameworkFacade.showToast(mContext, message);
+ Log.e(TAG, "Failed to modify mac randomization setting");
+ }
+ });
+ }
+ }
+
+ /**
+ * Class to show a AlertDialog which notifies the user of a network not being privacy
+ * compliant and then suggests an action.
+ */
+ private void showRandomizationSettingsDialog(int networkId, String ssidAndSecurityType) {
+ WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(networkId);
+ // Make sure the networkId is still pointing to the correct WifiConfiguration since
+ // there might be a large time gap between when the notification shows and when
+ // it's tapped.
+ if (config == null || ssidAndSecurityType == null
+ || !ssidAndSecurityType.equals(config.getSsidAndSecurityTypeString())) {
+ String message = mResources.getString(
+ R.string.wifi_disable_mac_randomization_dialog_network_not_found);
+ mFrameworkFacade.showToast(mContext, message);
+ return;
+ }
+
+ AlertDialog dialog = mConnectionFailureNotificationBuilder
+ .buildChangeMacRandomizationSettingDialog(config.SSID,
+ new DisableMacRandomizationListener(config));
+ dialog.show();
+ }
+}
diff --git a/service/java/com/android/server/wifi/FrameworkFacade.java b/service/java/com/android/server/wifi/FrameworkFacade.java
index 4fbe31867..fe3027e50 100644
--- a/service/java/com/android/server/wifi/FrameworkFacade.java
+++ b/service/java/com/android/server/wifi/FrameworkFacade.java
@@ -17,6 +17,7 @@
package com.android.server.wifi;
import android.app.ActivityManagerInternal;
+import android.app.AlertDialog;
import android.app.AppGlobals;
import android.app.Notification;
import android.app.PendingIntent;
@@ -35,6 +36,7 @@ import android.os.ServiceManager;
import android.os.storage.StorageManager;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
+import android.widget.Toast;
import com.android.internal.app.IBatteryStats;
import com.android.server.LocalServices;
@@ -227,4 +229,23 @@ public class FrameworkFacade {
public Notification.Builder makeNotificationBuilder(Context context, String channelId) {
return new Notification.Builder(context, channelId);
}
+
+ /**
+ * Create a new instance of {@link AlertDialog.Builder}.
+ * @param context reference to a Context
+ * @return an instance of AlertDialog.Builder
+ */
+ public AlertDialog.Builder makeAlertDialogBuilder(Context context) {
+ return new AlertDialog.Builder(context);
+ }
+
+ /**
+ * Show a toast message
+ * @param context reference to a Context
+ * @param text the message to display
+ */
+ public void showToast(Context context, String text) {
+ Toast toast = Toast.makeText(context, text, Toast.LENGTH_SHORT);
+ toast.show();
+ }
}
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index af8c14764..339e169f6 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.AppOpsManager;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.SystemSensorManager;
@@ -155,6 +156,7 @@ public class WifiInjector {
private final IpMemoryStore mIpMemoryStore;
private final CellularLinkLayerStatsCollector mCellularLinkLayerStatsCollector;
private final MacAddressUtil mMacAddressUtil;
+ private final ConnectionFailureNotificationBuilder mConnectionFailureNotificationBuilder;
public WifiInjector(Context context) {
if (context == null) {
@@ -172,6 +174,8 @@ public class WifiInjector {
mMacAddressUtil = new MacAddressUtil();
mContext = context;
mDeviceConfigFacade = new DeviceConfigFacade();
+ mConnectionFailureNotificationBuilder = new ConnectionFailureNotificationBuilder(
+ mContext, getWifiStackPackageName(), mFrameworkFacade);
mWifiScoreCard = new WifiScoreCard(mClock,
Secure.getString(mContext.getContentResolver(), Secure.ANDROID_ID));
mSettingsStore = new WifiSettingsStore(mContext);
@@ -623,6 +627,17 @@ public class WifiInjector {
}
/**
+ * Construct a new instance of ConnectionFailureNotifier.
+ * @param wifiConnectivityManager
+ * @return the created instance
+ */
+ public ConnectionFailureNotifier makeConnectionFailureNotifier(
+ WifiConnectivityManager wifiConnectivityManager) {
+ return new ConnectionFailureNotifier(mContext, this, mFrameworkFacade, mWifiConfigManager,
+ wifiConnectivityManager, new Handler(mWifiCoreHandlerThread.getLooper()));
+ }
+
+ /**
* Construct a new instance of {@link WifiNetworkFactory}.
* TODO(b/116233964): Remove cyclic dependency between WifiConnectivityManager & ClientModeImpl.
*/
@@ -701,6 +716,14 @@ public class WifiInjector {
return mMacAddressUtil;
}
+ public NotificationManager getNotificationManager() {
+ return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ public ConnectionFailureNotificationBuilder getConnectionFailureNotificationBuilder() {
+ return mConnectionFailureNotificationBuilder;
+ }
+
/**
* Returns a single instance of HalDeviceManager for injection.
*/
@@ -759,4 +782,8 @@ public class WifiInjector {
public HostapdHal getHostapdHal() {
return mHostapdHal;
}
+
+ public String getWifiStackPackageName() {
+ return mContext.getPackageName();
+ }
}
diff --git a/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java b/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java
index 9eb88b637..afb3ef5f4 100644
--- a/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java
@@ -384,7 +384,7 @@ public class ClientModeImplTest {
@Mock AsyncChannel mNullAsyncChannel;
@Mock CarrierNetworkConfig mCarrierNetworkConfig;
@Mock Handler mNetworkAgentHandler;
-
+ @Mock ConnectionFailureNotifier mConnectionFailureNotifier;
final ArgumentCaptor<WifiNative.InterfaceCallback> mInterfaceCallbackCaptor =
ArgumentCaptor.forClass(WifiNative.InterfaceCallback.class);
@@ -441,6 +441,8 @@ public class ClientModeImplTest {
when(mWifiInjector.getWifiScoreCard()).thenReturn(mWifiScoreCard);
when(mWifiInjector.getWifiLockManager()).thenReturn(mWifiLockManager);
when(mWifiInjector.getCarrierNetworkConfig()).thenReturn(mCarrierNetworkConfig);
+ when(mWifiInjector.makeConnectionFailureNotifier(any()))
+ .thenReturn(mConnectionFailureNotifier);
when(mWifiNetworkFactory.getSpecificNetworkRequestUidAndPackageName(any()))
.thenReturn(Pair.create(Process.INVALID_UID, ""));
when(mWifiNative.initialize()).thenReturn(true);
@@ -2816,6 +2818,44 @@ public class ClientModeImplTest {
}
/**
+ * Verifies that a notification is posted when a connection failure happens on a network
+ * in the hotlist. Then verify that tapping on the notification launches an dialog, which
+ * could be used to set the randomization setting for a network to "Trusted".
+ */
+ @Test
+ public void testConnectionFailureSendRandomizationSettingsNotification() throws Exception {
+ when(mWifiConfigManager.isInFlakyRandomizationSsidHotlist(anyInt())).thenReturn(true);
+ // Setup CONNECT_MODE & a WifiConfiguration
+ initializeAndAddNetworkAndVerifySuccess();
+ mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, FRAMEWORK_NETWORK_ID, 0, sBSSID);
+ mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
+ WifiManager.ERROR_AUTH_FAILURE_TIMEOUT);
+ mLooper.dispatchAll();
+
+ WifiConfiguration config = mCmi.getCurrentWifiConfiguration();
+ verify(mConnectionFailureNotifier)
+ .showFailedToConnectDueToNoRandomizedMacSupportNotification(FRAMEWORK_NETWORK_ID);
+ }
+
+ /**
+ * Verifies that a notification is not posted when a wrong password failure happens on a
+ * network in the hotlist.
+ */
+ @Test
+ public void testNotCallingIsInFlakyRandomizationSsidHotlistOnWrongPassword() throws Exception {
+ when(mWifiConfigManager.isInFlakyRandomizationSsidHotlist(anyInt())).thenReturn(true);
+ // Setup CONNECT_MODE & a WifiConfiguration
+ initializeAndAddNetworkAndVerifySuccess();
+ mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, FRAMEWORK_NETWORK_ID, 0, sBSSID);
+ mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
+ WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
+ mLooper.dispatchAll();
+
+ verify(mConnectionFailureNotifier, never())
+ .showFailedToConnectDueToNoRandomizedMacSupportNotification(anyInt());
+ }
+
+ /**
* Verifies that CMD_START_CONNECT make WifiDiagnostics report
* CONNECTION_EVENT_STARTED
* @throws Exception
diff --git a/tests/wifitests/src/com/android/server/wifi/ConnectionFailureNotifierTest.java b/tests/wifitests/src/com/android/server/wifi/ConnectionFailureNotifierTest.java
new file mode 100644
index 000000000..8bf07b8c3
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/ConnectionFailureNotifierTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2019 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 org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlertDialog;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.wifi.WifiConfiguration;
+import android.os.Handler;
+import android.os.Process;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link ConnectionFailureNotifier}.
+ */
+@SmallTest
+public class ConnectionFailureNotifierTest {
+ @Mock private Context mContext;
+ @Mock private WifiInjector mWifiInjector;
+ @Mock private Resources mResources;
+ @Mock private FrameworkFacade mFrameworkFacade;
+ @Mock private WifiConfigManager mWifiConfigManager;
+ @Mock private WifiConnectivityManager mWifiConnectivityManager;
+ @Mock private NotificationManager mNotificationManager;
+ @Mock private ConnectionFailureNotificationBuilder mConnectionFailureNotificationBuilder;
+ @Mock private Notification mNotification;
+ @Mock private AlertDialog mAlertDialog;
+
+ final ArgumentCaptor<BroadcastReceiver> mBroadCastReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ private ConnectionFailureNotifier mConnectionFailureNotifier;
+ TestLooper mLooper;
+
+ /** Initialize objects before each test run. */
+ @Before
+ public void setUp() throws Exception {
+ // Ensure looper exists
+ mLooper = new TestLooper();
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mWifiInjector.getNotificationManager()).thenReturn(mNotificationManager);
+ when(mWifiInjector.getConnectionFailureNotificationBuilder())
+ .thenReturn(mConnectionFailureNotificationBuilder);
+ when(mConnectionFailureNotificationBuilder
+ .buildNoMacRandomizationSupportNotification(any())).thenReturn(mNotification);
+ when(mConnectionFailureNotificationBuilder.buildChangeMacRandomizationSettingDialog(any(),
+ any())).thenReturn(mAlertDialog);
+ mConnectionFailureNotifier = new ConnectionFailureNotifier(mContext, mWifiInjector,
+ mFrameworkFacade, mWifiConfigManager, mWifiConnectivityManager,
+ new Handler(mLooper.getLooper()));
+
+ verify(mContext).registerReceiver(mBroadCastReceiverCaptor.capture(), any());
+ }
+
+ private class DisableMacRandomizationMatcher implements ArgumentMatcher<WifiConfiguration> {
+ @Override
+ public boolean matches(WifiConfiguration config) {
+ return config.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_NONE;
+ }
+ }
+
+ // Returns an intent that simulates the broadcast which is received when the user tap
+ // on the notification to change MAC randomization settings.
+ private Intent buildBroadcastForRandomizationSettingsDialog(WifiConfiguration config) {
+ Intent intent = mock(Intent.class);
+ when(intent.getAction()).thenReturn(ConnectionFailureNotificationBuilder
+ .ACTION_SHOW_SET_RANDOMIZATION_DETAILS);
+ when(intent.getIntExtra(eq(ConnectionFailureNotificationBuilder
+ .RANDOMIZATION_SETTINGS_NETWORK_ID), anyInt())).thenReturn(config.networkId);
+ when(intent.getStringExtra(
+ eq(ConnectionFailureNotificationBuilder.RANDOMIZATION_SETTINGS_NETWORK_SSID)))
+ .thenReturn(config.getSsidAndSecurityTypeString());
+ return intent;
+ }
+
+ /**
+ * Verify that a notification is posted when a connection failure happens on a network
+ * in the hotlist. Then verify that tapping on the notification launches an dialog, which
+ * could be used to set the randomization setting for a network to "Trusted".
+ */
+ @Test
+ public void testConnectionFailureSendRandomizationSettingsNotification() {
+ // Verify that the network is using randomized MAC at the start.
+ WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+ when(mWifiConfigManager.getConfiguredNetwork(config.networkId)).thenReturn(config);
+ assertEquals(WifiConfiguration.RANDOMIZATION_PERSISTENT, config.macRandomizationSetting);
+
+ mConnectionFailureNotifier.showFailedToConnectDueToNoRandomizedMacSupportNotification(
+ config.networkId);
+ // verify that a notification is sent
+ verify(mNotificationManager).notify(
+ eq(ConnectionFailureNotifier.NO_RANDOMIZED_MAC_SUPPORT_NOTIFICATION_ID),
+ eq(mNotification));
+
+ // sets up the intent that simulates the user tapping on the notification.
+ Intent intent = buildBroadcastForRandomizationSettingsDialog(config);
+
+ // simulate the user tapping on the notification, then verify the dialog shows up, and
+ // the appropriate callback is registered
+ ArgumentCaptor<DialogInterface.OnClickListener> onClickListenerArgumentCaptor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+ mBroadCastReceiverCaptor.getValue().onReceive(mContext, intent);
+ verify(mConnectionFailureNotificationBuilder).buildChangeMacRandomizationSettingDialog(
+ eq(config.SSID), onClickListenerArgumentCaptor.capture());
+
+ // simulate the user tapping on the option to reset MAC address to factory MAC
+ onClickListenerArgumentCaptor.getValue().onClick(null, 0);
+ mLooper.dispatchAll();
+
+ // verify the WifiConfiguration is updated properly.
+ verify(mWifiConfigManager).addOrUpdateNetwork(
+ argThat(new DisableMacRandomizationMatcher()), eq(Process.SYSTEM_UID));
+ // verify that we try to connect to the updated network.
+ verify(mWifiConnectivityManager).forceConnectivityScan(any());
+ }
+
+ /**
+ * Verify that if the WifiConfiguration if not found (may have been deleted by the timed the
+ * notification is tapped), then the AlertDialog does not show up.
+ */
+ @Test
+ public void testWifiConfigurationMismatch() {
+ WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+ when(mWifiConfigManager.getConfiguredNetwork(config.networkId)).thenReturn(config);
+ mConnectionFailureNotifier.showFailedToConnectDueToNoRandomizedMacSupportNotification(
+ config.networkId);
+ // verify that a notification is sent
+ verify(mNotificationManager).notify(
+ eq(ConnectionFailureNotifier.NO_RANDOMIZED_MAC_SUPPORT_NOTIFICATION_ID),
+ any());
+
+ // sets up the intent that simulates the user tapping on the notification.
+ Intent intent = buildBroadcastForRandomizationSettingsDialog(config);
+
+ // the WifiConfiguration that is found doesn't match with the one received from broadcast.
+ when(mWifiConfigManager.getConfiguredNetwork(anyInt()))
+ .thenReturn(WifiConfigurationTestUtil.createOpenNetwork());
+ mBroadCastReceiverCaptor.getValue().onReceive(mContext, intent);
+
+ // verify that the AlertDialog is not launched in this case
+ verify(mConnectionFailureNotificationBuilder, never())
+ .buildChangeMacRandomizationSettingDialog(any(), any());
+
+ verify(mFrameworkFacade, never()).makeAlertDialogBuilder(any());
+ // instead we are showings a toast due to failing to find the network
+ verify(mFrameworkFacade).showToast(any(), any());
+ }
+}