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