1/* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.wifi; 18 19import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_CONNECT_TO_NETWORK; 20import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_PICK_WIFI_NETWORK; 21import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE; 22import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_USER_DISMISSED_NOTIFICATION; 23import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.AVAILABLE_NETWORK_NOTIFIER_TAG; 24 25import android.annotation.IntDef; 26import android.annotation.NonNull; 27import android.app.Notification; 28import android.app.NotificationManager; 29import android.content.BroadcastReceiver; 30import android.content.Context; 31import android.content.Intent; 32import android.content.IntentFilter; 33import android.database.ContentObserver; 34import android.net.wifi.ScanResult; 35import android.net.wifi.WifiConfiguration; 36import android.net.wifi.WifiManager; 37import android.os.Handler; 38import android.os.Looper; 39import android.os.Message; 40import android.os.Messenger; 41import android.os.UserHandle; 42import android.os.UserManager; 43import android.provider.Settings; 44import android.text.TextUtils; 45import android.util.ArraySet; 46import android.util.Log; 47 48import com.android.internal.annotations.VisibleForTesting; 49import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount; 50import com.android.server.wifi.util.ScanResultUtil; 51 52import java.io.FileDescriptor; 53import java.io.PrintWriter; 54import java.lang.annotation.Retention; 55import java.lang.annotation.RetentionPolicy; 56import java.util.List; 57import java.util.Set; 58 59/** 60 * Base class for all network notifiers (e.g. OpenNetworkNotifier, CarrierNetworkNotifier). 61 * 62 * NOTE: These API's are not thread safe and should only be used from WifiStateMachine thread. 63 */ 64public class AvailableNetworkNotifier { 65 66 /** Time in milliseconds to display the Connecting notification. */ 67 private static final int TIME_TO_SHOW_CONNECTING_MILLIS = 10000; 68 69 /** Time in milliseconds to display the Connected notification. */ 70 private static final int TIME_TO_SHOW_CONNECTED_MILLIS = 5000; 71 72 /** Time in milliseconds to display the Failed To Connect notification. */ 73 private static final int TIME_TO_SHOW_FAILED_MILLIS = 5000; 74 75 /** The state of the notification */ 76 @IntDef({ 77 STATE_NO_NOTIFICATION, 78 STATE_SHOWING_RECOMMENDATION_NOTIFICATION, 79 STATE_CONNECTING_IN_NOTIFICATION, 80 STATE_CONNECTED_NOTIFICATION, 81 STATE_CONNECT_FAILED_NOTIFICATION 82 }) 83 @Retention(RetentionPolicy.SOURCE) 84 private @interface State {} 85 86 /** No recommendation is made and no notifications are shown. */ 87 private static final int STATE_NO_NOTIFICATION = 0; 88 /** The initial notification recommending a network to connect to is shown. */ 89 private static final int STATE_SHOWING_RECOMMENDATION_NOTIFICATION = 1; 90 /** The notification of status of connecting to the recommended network is shown. */ 91 private static final int STATE_CONNECTING_IN_NOTIFICATION = 2; 92 /** The notification that the connection to the recommended network was successful is shown. */ 93 private static final int STATE_CONNECTED_NOTIFICATION = 3; 94 /** The notification to show that connection to the recommended network failed is shown. */ 95 private static final int STATE_CONNECT_FAILED_NOTIFICATION = 4; 96 97 /** Current state of the notification. */ 98 @State private int mState = STATE_NO_NOTIFICATION; 99 100 /** 101 * The {@link Clock#getWallClockMillis()} must be at least this value for us 102 * to show the notification again. 103 */ 104 private long mNotificationRepeatTime; 105 /** 106 * When a notification is shown, we wait this amount before possibly showing it again. 107 */ 108 private final long mNotificationRepeatDelay; 109 /** Default repeat delay in seconds. */ 110 @VisibleForTesting 111 static final int DEFAULT_REPEAT_DELAY_SEC = 900; 112 113 /** Whether the user has set the setting to show the 'available networks' notification. */ 114 private boolean mSettingEnabled; 115 /** Whether the screen is on or not. */ 116 private boolean mScreenOn; 117 118 /** List of SSIDs blacklisted from recommendation. */ 119 private final Set<String> mBlacklistedSsids; 120 121 private final Context mContext; 122 private final Handler mHandler; 123 private final FrameworkFacade mFrameworkFacade; 124 private final WifiMetrics mWifiMetrics; 125 private final Clock mClock; 126 private final WifiConfigManager mConfigManager; 127 private final WifiStateMachine mWifiStateMachine; 128 private final Messenger mSrcMessenger; 129 private final ConnectToNetworkNotificationBuilder mNotificationBuilder; 130 131 private ScanResult mRecommendedNetwork; 132 133 /** Tag used for logs and metrics */ 134 private final String mTag; 135 /** Identifier of the {@link SsidSetStoreData}. */ 136 private final String mStoreDataIdentifier; 137 /** Identifier for the settings toggle, used for registering ContentObserver */ 138 private final String mToggleSettingsName; 139 140 /** System wide identifier for notification in Notification Manager */ 141 private final int mSystemMessageNotificationId; 142 143 public AvailableNetworkNotifier( 144 String tag, 145 String storeDataIdentifier, 146 String toggleSettingsName, 147 int notificationIdentifier, 148 Context context, 149 Looper looper, 150 FrameworkFacade framework, 151 Clock clock, 152 WifiMetrics wifiMetrics, 153 WifiConfigManager wifiConfigManager, 154 WifiConfigStore wifiConfigStore, 155 WifiStateMachine wifiStateMachine, 156 ConnectToNetworkNotificationBuilder connectToNetworkNotificationBuilder) { 157 mTag = tag; 158 mStoreDataIdentifier = storeDataIdentifier; 159 mToggleSettingsName = toggleSettingsName; 160 mSystemMessageNotificationId = notificationIdentifier; 161 mContext = context; 162 mHandler = new Handler(looper); 163 mFrameworkFacade = framework; 164 mWifiMetrics = wifiMetrics; 165 mClock = clock; 166 mConfigManager = wifiConfigManager; 167 mWifiStateMachine = wifiStateMachine; 168 mNotificationBuilder = connectToNetworkNotificationBuilder; 169 mScreenOn = false; 170 mSrcMessenger = new Messenger(new Handler(looper, mConnectionStateCallback)); 171 172 mBlacklistedSsids = new ArraySet<>(); 173 wifiConfigStore.registerStoreData(new SsidSetStoreData(mStoreDataIdentifier, 174 new AvailableNetworkNotifierStoreData())); 175 176 // Setting is in seconds 177 mNotificationRepeatDelay = mFrameworkFacade.getIntegerSetting(context, 178 Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 179 DEFAULT_REPEAT_DELAY_SEC) * 1000L; 180 NotificationEnabledSettingObserver settingObserver = new NotificationEnabledSettingObserver( 181 mHandler); 182 settingObserver.register(); 183 184 IntentFilter filter = new IntentFilter(); 185 filter.addAction(ACTION_USER_DISMISSED_NOTIFICATION); 186 filter.addAction(ACTION_CONNECT_TO_NETWORK); 187 filter.addAction(ACTION_PICK_WIFI_NETWORK); 188 filter.addAction(ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE); 189 mContext.registerReceiver( 190 mBroadcastReceiver, filter, null /* broadcastPermission */, mHandler); 191 } 192 193 private final BroadcastReceiver mBroadcastReceiver = 194 new BroadcastReceiver() { 195 @Override 196 public void onReceive(Context context, Intent intent) { 197 if (!mTag.equals(intent.getExtra(AVAILABLE_NETWORK_NOTIFIER_TAG))) { 198 return; 199 } 200 switch (intent.getAction()) { 201 case ACTION_USER_DISMISSED_NOTIFICATION: 202 handleUserDismissedAction(); 203 break; 204 case ACTION_CONNECT_TO_NETWORK: 205 handleConnectToNetworkAction(); 206 break; 207 case ACTION_PICK_WIFI_NETWORK: 208 handleSeeAllNetworksAction(); 209 break; 210 case ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE: 211 handlePickWifiNetworkAfterConnectFailure(); 212 break; 213 default: 214 Log.e(mTag, "Unknown action " + intent.getAction()); 215 } 216 } 217 }; 218 219 private final Handler.Callback mConnectionStateCallback = (Message msg) -> { 220 switch (msg.what) { 221 // Success here means that an attempt to connect to the network has been initiated. 222 // Successful connection updates are received via the 223 // WifiConnectivityManager#handleConnectionStateChanged() callback. 224 case WifiManager.CONNECT_NETWORK_SUCCEEDED: 225 break; 226 case WifiManager.CONNECT_NETWORK_FAILED: 227 handleConnectionAttemptFailedToSend(); 228 break; 229 default: 230 Log.e("AvailableNetworkNotifier", "Unknown message " + msg.what); 231 } 232 return true; 233 }; 234 235 /** 236 * Clears the pending notification. This is called by {@link WifiConnectivityManager} on stop. 237 * 238 * @param resetRepeatTime resets the time delay for repeated notification if true. 239 */ 240 public void clearPendingNotification(boolean resetRepeatTime) { 241 if (resetRepeatTime) { 242 mNotificationRepeatTime = 0; 243 } 244 245 if (mState != STATE_NO_NOTIFICATION) { 246 getNotificationManager().cancel(mSystemMessageNotificationId); 247 248 if (mRecommendedNetwork != null) { 249 Log.d(mTag, "Notification with state=" 250 + mState 251 + " was cleared for recommended network: " 252 + mRecommendedNetwork.SSID); 253 } 254 mState = STATE_NO_NOTIFICATION; 255 mRecommendedNetwork = null; 256 } 257 } 258 259 private boolean isControllerEnabled() { 260 return mSettingEnabled && !UserManager.get(mContext) 261 .hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT); 262 } 263 264 /** 265 * If there are available networks, attempt to post a network notification. 266 * 267 * @param availableNetworks Available networks to choose from and possibly show notification 268 */ 269 public void handleScanResults(@NonNull List<ScanDetail> availableNetworks) { 270 if (!isControllerEnabled()) { 271 clearPendingNotification(true /* resetRepeatTime */); 272 return; 273 } 274 if (availableNetworks.isEmpty() && mState == STATE_SHOWING_RECOMMENDATION_NOTIFICATION) { 275 clearPendingNotification(false /* resetRepeatTime */); 276 return; 277 } 278 279 // Not enough time has passed to show a recommendation notification again 280 if (mState == STATE_NO_NOTIFICATION 281 && mClock.getWallClockMillis() < mNotificationRepeatTime) { 282 return; 283 } 284 285 // Do nothing when the screen is off and no notification is showing. 286 if (mState == STATE_NO_NOTIFICATION && !mScreenOn) { 287 return; 288 } 289 290 // Only show a new or update an existing recommendation notification. 291 if (mState == STATE_NO_NOTIFICATION 292 || mState == STATE_SHOWING_RECOMMENDATION_NOTIFICATION) { 293 ScanResult recommendation = 294 recommendNetwork(availableNetworks, new ArraySet<>(mBlacklistedSsids)); 295 296 if (recommendation != null) { 297 postInitialNotification(recommendation); 298 } else { 299 clearPendingNotification(false /* resetRepeatTime */); 300 } 301 } 302 } 303 304 /** 305 * Recommends a network to connect to from a list of available networks, while ignoring the 306 * SSIDs in the blacklist. 307 */ 308 public ScanResult recommendNetwork(@NonNull List<ScanDetail> networks, 309 @NonNull Set<String> blacklistedSsids) { 310 ScanResult result = null; 311 int highestRssi = Integer.MIN_VALUE; 312 for (ScanDetail scanDetail : networks) { 313 ScanResult scanResult = scanDetail.getScanResult(); 314 315 if (scanResult.level > highestRssi) { 316 result = scanResult; 317 highestRssi = scanResult.level; 318 } 319 } 320 321 if (result != null && blacklistedSsids.contains(result.SSID)) { 322 result = null; 323 } 324 return result; 325 } 326 327 /** Handles screen state changes. */ 328 public void handleScreenStateChanged(boolean screenOn) { 329 mScreenOn = screenOn; 330 } 331 332 /** 333 * Called by {@link WifiConnectivityManager} when Wi-Fi is connected. If the notification 334 * was in the connecting state, update the notification to show that it has connected to the 335 * recommended network. 336 */ 337 public void handleWifiConnected() { 338 if (mState != STATE_CONNECTING_IN_NOTIFICATION) { 339 clearPendingNotification(true /* resetRepeatTime */); 340 return; 341 } 342 343 postNotification(mNotificationBuilder.createNetworkConnectedNotification(mTag, 344 mRecommendedNetwork)); 345 346 Log.d(mTag, "User connected to recommended network: " + mRecommendedNetwork.SSID); 347 mWifiMetrics.incrementConnectToNetworkNotification(mTag, 348 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTED_TO_NETWORK); 349 mState = STATE_CONNECTED_NOTIFICATION; 350 mHandler.postDelayed( 351 () -> { 352 if (mState == STATE_CONNECTED_NOTIFICATION) { 353 clearPendingNotification(true /* resetRepeatTime */); 354 } 355 }, 356 TIME_TO_SHOW_CONNECTED_MILLIS); 357 } 358 359 /** 360 * Handles when a Wi-Fi connection attempt failed. 361 */ 362 public void handleConnectionFailure() { 363 if (mState != STATE_CONNECTING_IN_NOTIFICATION) { 364 return; 365 } 366 postNotification(mNotificationBuilder.createNetworkFailedNotification(mTag)); 367 368 Log.d(mTag, "User failed to connect to recommended network: " + mRecommendedNetwork.SSID); 369 mWifiMetrics.incrementConnectToNetworkNotification(mTag, 370 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_FAILED_TO_CONNECT); 371 mState = STATE_CONNECT_FAILED_NOTIFICATION; 372 mHandler.postDelayed( 373 () -> { 374 if (mState == STATE_CONNECT_FAILED_NOTIFICATION) { 375 clearPendingNotification(false /* resetRepeatTime */); 376 } 377 }, 378 TIME_TO_SHOW_FAILED_MILLIS); 379 } 380 381 private NotificationManager getNotificationManager() { 382 return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); 383 } 384 385 private void postInitialNotification(ScanResult recommendedNetwork) { 386 if (mRecommendedNetwork != null 387 && TextUtils.equals(mRecommendedNetwork.SSID, recommendedNetwork.SSID)) { 388 return; 389 } 390 391 postNotification(mNotificationBuilder.createConnectToAvailableNetworkNotification(mTag, 392 recommendedNetwork)); 393 394 if (mState == STATE_NO_NOTIFICATION) { 395 mWifiMetrics.incrementConnectToNetworkNotification(mTag, 396 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK); 397 } else { 398 mWifiMetrics.incrementNumNetworkRecommendationUpdates(mTag); 399 } 400 mState = STATE_SHOWING_RECOMMENDATION_NOTIFICATION; 401 mRecommendedNetwork = recommendedNetwork; 402 mNotificationRepeatTime = mClock.getWallClockMillis() + mNotificationRepeatDelay; 403 } 404 405 private void postNotification(Notification notification) { 406 getNotificationManager().notify(mSystemMessageNotificationId, notification); 407 } 408 409 private void handleConnectToNetworkAction() { 410 mWifiMetrics.incrementConnectToNetworkNotificationAction(mTag, mState, 411 ConnectToNetworkNotificationAndActionCount.ACTION_CONNECT_TO_NETWORK); 412 if (mState != STATE_SHOWING_RECOMMENDATION_NOTIFICATION) { 413 return; 414 } 415 postNotification(mNotificationBuilder.createNetworkConnectingNotification(mTag, 416 mRecommendedNetwork)); 417 mWifiMetrics.incrementConnectToNetworkNotification(mTag, 418 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTING_TO_NETWORK); 419 420 Log.d(mTag, 421 "User initiated connection to recommended network: " + mRecommendedNetwork.SSID); 422 WifiConfiguration network = createRecommendedNetworkConfig(mRecommendedNetwork); 423 Message msg = Message.obtain(); 424 msg.what = WifiManager.CONNECT_NETWORK; 425 msg.arg1 = WifiConfiguration.INVALID_NETWORK_ID; 426 msg.obj = network; 427 msg.replyTo = mSrcMessenger; 428 mWifiStateMachine.sendMessage(msg); 429 430 mState = STATE_CONNECTING_IN_NOTIFICATION; 431 mHandler.postDelayed( 432 () -> { 433 if (mState == STATE_CONNECTING_IN_NOTIFICATION) { 434 handleConnectionFailure(); 435 } 436 }, 437 TIME_TO_SHOW_CONNECTING_MILLIS); 438 } 439 440 WifiConfiguration createRecommendedNetworkConfig(ScanResult recommendedNetwork) { 441 return ScanResultUtil.createNetworkFromScanResult(recommendedNetwork); 442 } 443 444 private void handleSeeAllNetworksAction() { 445 mWifiMetrics.incrementConnectToNetworkNotificationAction(mTag, mState, 446 ConnectToNetworkNotificationAndActionCount.ACTION_PICK_WIFI_NETWORK); 447 startWifiSettings(); 448 } 449 450 private void startWifiSettings() { 451 // Close notification drawer before opening the picker. 452 mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); 453 mContext.startActivity( 454 new Intent(Settings.ACTION_WIFI_SETTINGS) 455 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 456 clearPendingNotification(false /* resetRepeatTime */); 457 } 458 459 private void handleConnectionAttemptFailedToSend() { 460 handleConnectionFailure(); 461 mWifiMetrics.incrementNumNetworkConnectMessageFailedToSend(mTag); 462 } 463 464 private void handlePickWifiNetworkAfterConnectFailure() { 465 mWifiMetrics.incrementConnectToNetworkNotificationAction(mTag, mState, 466 ConnectToNetworkNotificationAndActionCount 467 .ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE); 468 startWifiSettings(); 469 } 470 471 private void handleUserDismissedAction() { 472 Log.d(mTag, "User dismissed notification with state=" + mState); 473 mWifiMetrics.incrementConnectToNetworkNotificationAction(mTag, mState, 474 ConnectToNetworkNotificationAndActionCount.ACTION_USER_DISMISSED_NOTIFICATION); 475 if (mState == STATE_SHOWING_RECOMMENDATION_NOTIFICATION) { 476 // blacklist dismissed network 477 mBlacklistedSsids.add(mRecommendedNetwork.SSID); 478 mWifiMetrics.setNetworkRecommenderBlacklistSize(mTag, mBlacklistedSsids.size()); 479 mConfigManager.saveToStore(false /* forceWrite */); 480 Log.d(mTag, "Network is added to the network notification blacklist: " 481 + mRecommendedNetwork.SSID); 482 } 483 resetStateAndDelayNotification(); 484 } 485 486 private void resetStateAndDelayNotification() { 487 mState = STATE_NO_NOTIFICATION; 488 mNotificationRepeatTime = System.currentTimeMillis() + mNotificationRepeatDelay; 489 mRecommendedNetwork = null; 490 } 491 492 /** Dump this network notifier's state. */ 493 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 494 pw.println(mTag + ": "); 495 pw.println("mSettingEnabled " + mSettingEnabled); 496 pw.println("currentTime: " + mClock.getWallClockMillis()); 497 pw.println("mNotificationRepeatTime: " + mNotificationRepeatTime); 498 pw.println("mState: " + mState); 499 pw.println("mBlacklistedSsids: " + mBlacklistedSsids.toString()); 500 } 501 502 private class AvailableNetworkNotifierStoreData implements SsidSetStoreData.DataSource { 503 @Override 504 public Set<String> getSsids() { 505 return new ArraySet<>(mBlacklistedSsids); 506 } 507 508 @Override 509 public void setSsids(Set<String> ssidList) { 510 mBlacklistedSsids.addAll(ssidList); 511 mWifiMetrics.setNetworkRecommenderBlacklistSize(mTag, mBlacklistedSsids.size()); 512 } 513 } 514 515 private class NotificationEnabledSettingObserver extends ContentObserver { 516 NotificationEnabledSettingObserver(Handler handler) { 517 super(handler); 518 } 519 520 public void register() { 521 mFrameworkFacade.registerContentObserver(mContext, 522 Settings.Global.getUriFor(mToggleSettingsName), true, this); 523 mSettingEnabled = getValue(); 524 } 525 526 @Override 527 public void onChange(boolean selfChange) { 528 super.onChange(selfChange); 529 mSettingEnabled = getValue(); 530 clearPendingNotification(true /* resetRepeatTime */); 531 } 532 533 private boolean getValue() { 534 boolean enabled = 535 mFrameworkFacade.getIntegerSetting(mContext, mToggleSettingsName, 1) == 1; 536 mWifiMetrics.setIsWifiNetworksAvailableNotificationEnabled(mTag, enabled); 537 Log.d(mTag, "Settings toggle enabled=" + enabled); 538 return enabled; 539 } 540 } 541} 542