diff options
6 files changed, 247 insertions, 3 deletions
diff --git a/service/java/com/android/server/wifi/FrameworkFacade.java b/service/java/com/android/server/wifi/FrameworkFacade.java index ba114df8a..760ee697a 100644 --- a/service/java/com/android/server/wifi/FrameworkFacade.java +++ b/service/java/com/android/server/wifi/FrameworkFacade.java @@ -18,6 +18,7 @@ package com.android.server.wifi; import android.app.ActivityManager; import android.app.AppGlobals; +import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -94,6 +95,13 @@ public class FrameworkFacade { return PendingIntent.getBroadcast(context, requestCode, intent, flags); } + /** + * Wrapper for {@link PendingIntent#getActivity}. + */ + public PendingIntent getActivity(Context context, int requestCode, Intent intent, int flags) { + return PendingIntent.getActivity(context, requestCode, intent, flags); + } + public SupplicantStateTracker makeSupplicantStateTracker(Context context, WifiConfigManager configManager, Handler handler) { return new SupplicantStateTracker(context, configManager, this, handler); @@ -172,4 +180,14 @@ public class FrameworkFacade { public boolean isAppForeground(int uid) throws RemoteException { return ActivityManager.getService().isAppForeground(uid); } + + /** + * Create a new instance of {@link Notification.Builder}. + * @param context reference to a Context + * @param channelId ID of the notification channel + * @return an instance of Notification.Builder + */ + public Notification.Builder makeNotificationBuilder(Context context, String channelId) { + return new Notification.Builder(context, channelId); + } } diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java index 0b0c38002..e1be8a3a9 100644 --- a/service/java/com/android/server/wifi/WifiInjector.java +++ b/service/java/com/android/server/wifi/WifiInjector.java @@ -224,7 +224,8 @@ public class WifiInjector { mJavaRuntime = Runtime.getRuntime(); mWifiStateMachine = new WifiStateMachine(mContext, mFrameworkFacade, wifiStateMachineLooper, UserManager.get(mContext), - this, mBackupManagerProxy, mCountryCode, mWifiNative); + this, mBackupManagerProxy, mCountryCode, mWifiNative, + new WrongPasswordNotifier(mContext, mFrameworkFacade)); mCertManager = new WifiCertManager(mContext); mNotificationController = new WifiNotificationController(mContext, mWifiServiceHandlerThread.getLooper(), mFrameworkFacade, null, this); diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java index 183f32ff0..c6998d31b 100644 --- a/service/java/com/android/server/wifi/WifiStateMachine.java +++ b/service/java/com/android/server/wifi/WifiStateMachine.java @@ -898,11 +898,13 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss private FrameworkFacade mFacade; private WifiStateTracker mWifiStateTracker; private final BackupManagerProxy mBackupManagerProxy; + private final WrongPasswordNotifier mWrongPasswordNotifier; public WifiStateMachine(Context context, FrameworkFacade facade, Looper looper, UserManager userManager, WifiInjector wifiInjector, BackupManagerProxy backupManagerProxy, WifiCountryCode countryCode, - WifiNative wifiNative) { + WifiNative wifiNative, + WrongPasswordNotifier wrongPasswordNotifier) { super("WifiStateMachine", looper); mWifiInjector = wifiInjector; mWifiMetrics = mWifiInjector.getWifiMetrics(); @@ -913,6 +915,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss mFacade = facade; mWifiNative = wifiNative; mBackupManagerProxy = backupManagerProxy; + mWrongPasswordNotifier = wrongPasswordNotifier; // TODO refactor WifiNative use of context out into it's own class mInterfaceName = mWifiNative.getInterfaceName(); @@ -3409,6 +3412,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss mDiagsConnectionStartMillis = mClock.getElapsedSinceBootMillis(); mWifiDiagnostics.reportConnectionEvent( mDiagsConnectionStartMillis, WifiDiagnostics.CONNECTION_EVENT_STARTED); + mWrongPasswordNotifier.onNewConnectionAttempt(); // TODO(b/35329124): Remove CMD_DIAGS_CONNECT_TIMEOUT, once WifiStateMachine // grows a proper CONNECTING state. sendMessageDelayed(CMD_DIAGS_CONNECT_TIMEOUT, @@ -4960,6 +4964,12 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss if (isPermanentWrongPasswordFailure(mTargetNetworkId, message.arg2)) { disableReason = WifiConfiguration.NetworkSelectionStatus .DISABLED_BY_WRONG_PASSWORD; + WifiConfiguration targetedNetwork = + mWifiConfigManager.getConfiguredNetwork(mTargetNetworkId); + if (targetedNetwork != null) { + mWrongPasswordNotifier.onWrongPasswordError( + targetedNetwork.SSID); + } } mWifiConfigManager.updateNetworkSelectionStatus( mTargetNetworkId, disableReason); diff --git a/service/java/com/android/server/wifi/WrongPasswordNotifier.java b/service/java/com/android/server/wifi/WrongPasswordNotifier.java new file mode 100644 index 000000000..5447322d3 --- /dev/null +++ b/service/java/com/android/server/wifi/WrongPasswordNotifier.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017 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.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.net.wifi.WifiManager; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.internal.notification.SystemNotificationChannels; + +/** + * Responsible for notifying user for wrong password errors. + */ +public class WrongPasswordNotifier { + // Number of milliseconds to wait before automatically dismiss the notification. + private static final long CANCEL_TIMEOUT_MILLISECONDS = 5 * 60 * 1000; + + // Unique ID associated with the notification. + @VisibleForTesting + public static final int NOTIFICATION_ID = SystemMessage.NOTE_WIFI_WRONG_PASSWORD; + + // Flag indicating if a wrong password error is detected for the current connection. + private boolean mWrongPasswordDetected; + + private final Context mContext; + private final NotificationManager mNotificationManager; + private final FrameworkFacade mFrameworkFacade; + + public WrongPasswordNotifier(Context context, FrameworkFacade frameworkFacade) { + mContext = context; + mFrameworkFacade = frameworkFacade; + mNotificationManager = + (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + } + + /** + * Invoked when a wrong password error for a Wi-Fi network is detected. + * + * @param ssid The SSID of the Wi-Fi network + */ + public void onWrongPasswordError(String ssid) { + showNotification(ssid); + mWrongPasswordDetected = true; + } + + /** + * Invoked when attempting a new Wi-Fi network connection. + */ + public void onNewConnectionAttempt() { + if (mWrongPasswordDetected) { + dismissNotification(); + mWrongPasswordDetected = false; + } + } + + /** + * Display wrong password notification for a given Wi-Fi network (specified by its SSID). + * + * @param ssid SSID of the Wi-FI network + */ + private void showNotification(String ssid) { + Notification.Builder builder = mFrameworkFacade.makeNotificationBuilder(mContext, + SystemNotificationChannels.NETWORK_ALERTS) + .setAutoCancel(true) + .setTimeoutAfter(CANCEL_TIMEOUT_MILLISECONDS) + // TODO(zqiu): consider creating a new icon. + .setSmallIcon(com.android.internal.R.drawable.stat_notify_wifi_in_range) + .setContentTitle(mContext.getString( + com.android.internal.R.string.wifi_available_title_failed_to_connect)) + .setContentText(ssid) + // TODO(zqiu): update to point to the new activity when it is ready. + .setContentIntent(mFrameworkFacade.getActivity( + mContext, 0, new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK), + PendingIntent.FLAG_UPDATE_CURRENT)) + .setColor(mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color)); + mNotificationManager.notify(NOTIFICATION_ID, builder.build()); + } + + /** + * Dismiss the notification that was generated by {@link #showNotification}. The notification + * might have already been dismissed, either by user or timeout. We'll attempt to dismiss it + * regardless if it is been dismissed or not, to reduce code complexity. + */ + private void dismissNotification() { + // Notification might have already been dismissed, either by user or timeout. It is + // still okay to cancel it if already dismissed. + mNotificationManager.cancel(null, NOTIFICATION_ID); + } +} diff --git a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java index 52b4d3687..71f29b4f6 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java @@ -344,6 +344,7 @@ public class WifiStateMachineTest { @Mock SelfRecovery mSelfRecovery; @Mock IpManager mIpManager; @Mock TelephonyManager mTelephonyManager; + @Mock WrongPasswordNotifier mWrongPasswordNotifier; public WifiStateMachineTest() throws Exception { } @@ -436,7 +437,8 @@ public class WifiStateMachineTest { private void initializeWsm() throws Exception { mWsm = new WifiStateMachine(mContext, mFrameworkFacade, mLooper.getLooper(), - mUserManager, mWifiInjector, mBackupManagerProxy, mCountryCode, mWifiNative); + mUserManager, mWifiInjector, mBackupManagerProxy, mCountryCode, mWifiNative, + mWrongPasswordNotifier); mWsmThread = getWsmHandlerThread(mWsm); final AsyncChannel channel = new AsyncChannel(); @@ -1113,6 +1115,7 @@ public class WifiStateMachineTest { WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD); mLooper.dispatchAll(); + verify(mWrongPasswordNotifier, never()).onWrongPasswordError(anyString()); verify(mWifiConfigManager).updateNetworkSelectionStatus(anyInt(), eq(WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE)); @@ -1138,6 +1141,7 @@ public class WifiStateMachineTest { verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt()); WifiConfiguration config = new WifiConfiguration(); + config.SSID = sSSID; config.getNetworkSelectionStatus().setHasEverConnected(false); when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(config); @@ -1145,6 +1149,7 @@ public class WifiStateMachineTest { WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD); mLooper.dispatchAll(); + verify(mWrongPasswordNotifier).onWrongPasswordError(eq(sSSID)); verify(mWifiConfigManager).updateNetworkSelectionStatus(anyInt(), eq(WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD)); diff --git a/tests/wifitests/src/com/android/server/wifi/WrongPasswordNotifierTest.java b/tests/wifitests/src/com/android/server/wifi/WrongPasswordNotifierTest.java new file mode 100644 index 000000000..405ab65c2 --- /dev/null +++ b/tests/wifitests/src/com/android/server/wifi/WrongPasswordNotifierTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 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.*; +import static org.mockito.Mockito.*; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.content.res.Resources; + +import com.android.internal.notification.SystemNotificationChannels; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link com.android.server.wifi.WrongPasswordNotifier}. + */ +public class WrongPasswordNotifierTest { + private static final String TEST_SSID = "Test SSID"; + + @Mock Context mContext; + @Mock Resources mResources; + @Mock NotificationManager mNotificationManager; + @Mock FrameworkFacade mFrameworkFacade; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Notification.Builder mNotificationBuilder; + WrongPasswordNotifier mWrongPassNotifier; + + /** + * Sets up for unit test + */ + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)) + .thenReturn(mNotificationManager); + when(mContext.getResources()).thenReturn(mResources); + mWrongPassNotifier = + new WrongPasswordNotifier(mContext, mFrameworkFacade); + } + + /** + * Verify that a wrong password notification will be generated/pushed when a wrong password + * error is detected for the current connection. + * + * @throws Exception + */ + @Test + public void onWrongPasswordError() throws Exception { + when(mFrameworkFacade.makeNotificationBuilder(any(), + eq(SystemNotificationChannels.NETWORK_ALERTS))).thenReturn(mNotificationBuilder); + mWrongPassNotifier.onWrongPasswordError(TEST_SSID); + verify(mNotificationManager).notify(eq(WrongPasswordNotifier.NOTIFICATION_ID), any()); + } + + /** + * Verify that we will attempt to dismiss the wrong password notification when starting a new + * connection attempt with the previous connection resulting in a wrong password error. + * + * @throws Exception + */ + @Test + public void onNewConnectionAttemptWithPreviousWrongPasswordError() throws Exception { + onWrongPasswordError(); + reset(mNotificationManager); + + mWrongPassNotifier.onNewConnectionAttempt(); + verify(mNotificationManager).cancel(any(), eq(WrongPasswordNotifier.NOTIFICATION_ID)); + } + + /** + * Verify that we don't attempt to dismiss the wrong password notification when starting a new + * connection attempt with the previous connection not resulting in a wrong password error. + * + * @throws Exception + */ + @Test + public void onNewConnectionAttemptWithoutPreviousWrongPasswordError() throws Exception { + mWrongPassNotifier.onNewConnectionAttempt(); + verify(mNotificationManager, never()).cancel(any(), anyInt()); + } +} |