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 17 package com.android.server.wifi; 18 19 import static android.app.AppOpsManager.MODE_IGNORED; 20 import static android.app.AppOpsManager.OPSTR_CHANGE_WIFI_STATE; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.app.AppOpsManager; 25 import android.app.Notification; 26 import android.app.NotificationManager; 27 import android.app.PendingIntent; 28 import android.content.BroadcastReceiver; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.content.pm.ApplicationInfo; 33 import android.content.pm.PackageManager; 34 import android.content.res.Resources; 35 import android.net.MacAddress; 36 import android.net.wifi.ScanResult; 37 import android.net.wifi.WifiConfiguration; 38 import android.net.wifi.WifiManager; 39 import android.net.wifi.WifiNetworkSuggestion; 40 import android.os.Handler; 41 import android.os.UserHandle; 42 import android.text.TextUtils; 43 import android.util.Log; 44 import android.util.Pair; 45 46 import com.android.internal.R; 47 import com.android.internal.annotations.VisibleForTesting; 48 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; 49 import com.android.internal.notification.SystemNotificationChannels; 50 import com.android.server.wifi.util.WifiPermissionsUtil; 51 52 import java.io.FileDescriptor; 53 import java.io.PrintWriter; 54 import java.util.Collection; 55 import java.util.Collections; 56 import java.util.HashMap; 57 import java.util.HashSet; 58 import java.util.Iterator; 59 import java.util.List; 60 import java.util.Map; 61 import java.util.Objects; 62 import java.util.Set; 63 import java.util.stream.Collectors; 64 65 import javax.annotation.concurrent.NotThreadSafe; 66 67 /** 68 * Network Suggestions Manager. 69 * NOTE: This class should always be invoked from the main wifi service thread. 70 */ 71 @NotThreadSafe 72 public class WifiNetworkSuggestionsManager { 73 private static final String TAG = "WifiNetworkSuggestionsManager"; 74 75 /** Intent when user tapped action button to allow the app. */ 76 @VisibleForTesting 77 public static final String NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION = 78 "com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_APP"; 79 /** Intent when user tapped action button to disallow the app. */ 80 @VisibleForTesting 81 public static final String NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION = 82 "com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_APP"; 83 /** Intent when user dismissed the notification. */ 84 @VisibleForTesting 85 public static final String NOTIFICATION_USER_DISMISSED_INTENT_ACTION = 86 "com.android.server.wifi.action.NetworkSuggestion.USER_DISMISSED"; 87 @VisibleForTesting 88 public static final String EXTRA_PACKAGE_NAME = 89 "com.android.server.wifi.extra.NetworkSuggestion.PACKAGE_NAME"; 90 @VisibleForTesting 91 public static final String EXTRA_UID = 92 "com.android.server.wifi.extra.NetworkSuggestion.UID"; 93 94 private final Context mContext; 95 private final Resources mResources; 96 private final Handler mHandler; 97 private final AppOpsManager mAppOps; 98 private final NotificationManager mNotificationManager; 99 private final PackageManager mPackageManager; 100 private final WifiPermissionsUtil mWifiPermissionsUtil; 101 private final WifiConfigManager mWifiConfigManager; 102 private final WifiMetrics mWifiMetrics; 103 private final WifiInjector mWifiInjector; 104 private final FrameworkFacade mFrameworkFacade; 105 private final WifiKeyStore mWifiKeyStore; 106 107 /** 108 * Per app meta data to store network suggestions, status, etc for each app providing network 109 * suggestions on the device. 110 */ 111 public static class PerAppInfo { 112 /** 113 * Package Name of the app. 114 */ 115 public final String packageName; 116 /** 117 * Set of active network suggestions provided by the app. 118 */ 119 public final Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = new HashSet<>(); 120 /** 121 * Whether we have shown the user a notification for this app. 122 */ 123 public boolean hasUserApproved = false; 124 125 /** Stores the max size of the {@link #extNetworkSuggestions} list ever for this app */ 126 public int maxSize = 0; 127 PerAppInfo(@onNull String packageName)128 public PerAppInfo(@NonNull String packageName) { 129 this.packageName = packageName; 130 } 131 132 // This is only needed for comparison in unit tests. 133 @Override equals(Object other)134 public boolean equals(Object other) { 135 if (other == null) return false; 136 if (!(other instanceof PerAppInfo)) return false; 137 PerAppInfo otherPerAppInfo = (PerAppInfo) other; 138 return TextUtils.equals(packageName, otherPerAppInfo.packageName) 139 && Objects.equals(extNetworkSuggestions, otherPerAppInfo.extNetworkSuggestions) 140 && hasUserApproved == otherPerAppInfo.hasUserApproved; 141 } 142 143 // This is only needed for comparison in unit tests. 144 @Override hashCode()145 public int hashCode() { 146 return Objects.hash(packageName, extNetworkSuggestions, hasUserApproved); 147 } 148 } 149 150 /** 151 * Internal container class which holds a network suggestion and a pointer to the 152 * {@link PerAppInfo} entry from {@link #mActiveNetworkSuggestionsPerApp} corresponding to the 153 * app that made the suggestion. 154 */ 155 public static class ExtendedWifiNetworkSuggestion { 156 public final WifiNetworkSuggestion wns; 157 // Store the pointer to the corresponding app's meta data. 158 public final PerAppInfo perAppInfo; 159 ExtendedWifiNetworkSuggestion(@onNull WifiNetworkSuggestion wns, @NonNull PerAppInfo perAppInfo)160 public ExtendedWifiNetworkSuggestion(@NonNull WifiNetworkSuggestion wns, 161 @NonNull PerAppInfo perAppInfo) { 162 this.wns = wns; 163 this.perAppInfo = perAppInfo; 164 this.wns.wifiConfiguration.fromWifiNetworkSuggestion = true; 165 this.wns.wifiConfiguration.ephemeral = true; 166 this.wns.wifiConfiguration.creatorName = perAppInfo.packageName; 167 this.wns.wifiConfiguration.creatorUid = wns.suggestorUid; 168 } 169 170 @Override hashCode()171 public int hashCode() { 172 return Objects.hash(wns); // perAppInfo not used for equals. 173 } 174 175 @Override equals(Object obj)176 public boolean equals(Object obj) { 177 if (this == obj) { 178 return true; 179 } 180 if (!(obj instanceof ExtendedWifiNetworkSuggestion)) { 181 return false; 182 } 183 ExtendedWifiNetworkSuggestion other = (ExtendedWifiNetworkSuggestion) obj; 184 return wns.equals(other.wns); // perAppInfo not used for equals. 185 } 186 187 @Override toString()188 public String toString() { 189 return "Extended" + wns.toString(); 190 } 191 192 /** 193 * Convert from {@link WifiNetworkSuggestion} to a new instance of 194 * {@link ExtendedWifiNetworkSuggestion}. 195 */ fromWns( @onNull WifiNetworkSuggestion wns, @NonNull PerAppInfo perAppInfo)196 public static ExtendedWifiNetworkSuggestion fromWns( 197 @NonNull WifiNetworkSuggestion wns, @NonNull PerAppInfo perAppInfo) { 198 return new ExtendedWifiNetworkSuggestion(wns, perAppInfo); 199 } 200 } 201 202 /** 203 * Map of package name of an app to the set of active network suggestions provided by the app. 204 */ 205 private final Map<String, PerAppInfo> mActiveNetworkSuggestionsPerApp = new HashMap<>(); 206 /** 207 * Map of package name of an app to the app ops changed listener for the app. 208 */ 209 private final Map<String, AppOpsChangedListener> mAppOpsChangedListenerPerApp = new HashMap<>(); 210 /** 211 * Map maintained to help lookup all the network suggestions (with no bssid) that match a 212 * provided scan result. 213 * Note: 214 * <li>There could be multiple suggestions (provided by different apps) that match a single 215 * scan result.</li> 216 * <li>Adding/Removing to this set for scan result lookup is expensive. But, we expect scan 217 * result lookup to happen much more often than apps modifying network suggestions.</li> 218 */ 219 private final Map<ScanResultMatchInfo, Set<ExtendedWifiNetworkSuggestion>> 220 mActiveScanResultMatchInfoWithNoBssid = new HashMap<>(); 221 /** 222 * Map maintained to help lookup all the network suggestions (with bssid) that match a provided 223 * scan result. 224 * Note: 225 * <li>There could be multiple suggestions (provided by different apps) that match a single 226 * scan result.</li> 227 * <li>Adding/Removing to this set for scan result lookup is expensive. But, we expect scan 228 * result lookup to happen much more often than apps modifying network suggestions.</li> 229 */ 230 private final Map<Pair<ScanResultMatchInfo, MacAddress>, Set<ExtendedWifiNetworkSuggestion>> 231 mActiveScanResultMatchInfoWithBssid = new HashMap<>(); 232 /** 233 * List of {@link WifiNetworkSuggestion} matching the current connected network. 234 */ 235 private Set<ExtendedWifiNetworkSuggestion> mActiveNetworkSuggestionsMatchingConnection; 236 237 /** 238 * Intent filter for processing notification actions. 239 */ 240 private final IntentFilter mIntentFilter; 241 242 /** 243 * Verbose logging flag. 244 */ 245 private boolean mVerboseLoggingEnabled = false; 246 /** 247 * Indicates that we have new data to serialize. 248 */ 249 private boolean mHasNewDataToSerialize = false; 250 /** 251 * Indicates if the user approval notification is active. 252 */ 253 private boolean mUserApprovalNotificationActive = false; 254 /** 255 * Stores the name of the user approval notification that is active. 256 */ 257 private String mUserApprovalNotificationPackageName; 258 259 /** 260 * Listener for app-ops changes for active suggestor apps. 261 */ 262 private final class AppOpsChangedListener implements AppOpsManager.OnOpChangedListener { 263 private final String mPackageName; 264 private final int mUid; 265 AppOpsChangedListener(@onNull String packageName, int uid)266 AppOpsChangedListener(@NonNull String packageName, int uid) { 267 mPackageName = packageName; 268 mUid = uid; 269 } 270 271 @Override onOpChanged(String op, String packageName)272 public void onOpChanged(String op, String packageName) { 273 mHandler.post(() -> { 274 if (!mPackageName.equals(packageName)) return; 275 if (!OPSTR_CHANGE_WIFI_STATE.equals(op)) return; 276 277 // Ensure the uid to package mapping is still correct. 278 try { 279 mAppOps.checkPackage(mUid, mPackageName); 280 } catch (SecurityException e) { 281 Log.wtf(TAG, "Invalid uid/package" + packageName); 282 return; 283 } 284 285 if (mAppOps.unsafeCheckOpNoThrow(OPSTR_CHANGE_WIFI_STATE, mUid, mPackageName) 286 == AppOpsManager.MODE_IGNORED) { 287 Log.i(TAG, "User disallowed change wifi state for " + packageName); 288 // User disabled the app, remove app from database. We want the notification 289 // again if the user enabled the app-op back. 290 removeApp(mPackageName); 291 } 292 }); 293 } 294 }; 295 296 /** 297 * Module to interact with the wifi config store. 298 */ 299 private class NetworkSuggestionDataSource implements NetworkSuggestionStoreData.DataSource { 300 @Override toSerialize()301 public Map<String, PerAppInfo> toSerialize() { 302 // Clear the flag after writing to disk. 303 // TODO(b/115504887): Don't reset the flag on write failure. 304 mHasNewDataToSerialize = false; 305 return mActiveNetworkSuggestionsPerApp; 306 } 307 308 @Override 309 fromDeserialized(Map<String, PerAppInfo> networkSuggestionsMap)310 public void fromDeserialized(Map<String, PerAppInfo> networkSuggestionsMap) { 311 mActiveNetworkSuggestionsPerApp.putAll(networkSuggestionsMap); 312 // Build the scan cache. 313 for (Map.Entry<String, PerAppInfo> entry : networkSuggestionsMap.entrySet()) { 314 String packageName = entry.getKey(); 315 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = 316 entry.getValue().extNetworkSuggestions; 317 if (!extNetworkSuggestions.isEmpty()) { 318 // Start tracking app-op changes from the app if they have active suggestions. 319 startTrackingAppOpsChange(packageName, 320 extNetworkSuggestions.iterator().next().wns.suggestorUid); 321 } 322 addToScanResultMatchInfoMap(extNetworkSuggestions); 323 } 324 } 325 326 @Override reset()327 public void reset() { 328 mActiveNetworkSuggestionsPerApp.clear(); 329 mActiveScanResultMatchInfoWithBssid.clear(); 330 mActiveScanResultMatchInfoWithNoBssid.clear(); 331 } 332 333 @Override hasNewDataToSerialize()334 public boolean hasNewDataToSerialize() { 335 return mHasNewDataToSerialize; 336 } 337 } 338 339 private final BroadcastReceiver mBroadcastReceiver = 340 new BroadcastReceiver() { 341 @Override 342 public void onReceive(Context context, Intent intent) { 343 String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME); 344 if (packageName == null) { 345 Log.e(TAG, "No package name found in intent"); 346 return; 347 } 348 int uid = intent.getIntExtra(EXTRA_UID, -1); 349 if (uid == -1) { 350 Log.e(TAG, "No uid found in intent"); 351 return; 352 } 353 switch (intent.getAction()) { 354 case NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION: 355 Log.i(TAG, "User clicked to allow app"); 356 // Set the user approved flag. 357 setHasUserApprovedForApp(true, packageName); 358 break; 359 case NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION: 360 Log.i(TAG, "User clicked to disallow app"); 361 // Set the user approved flag. 362 setHasUserApprovedForApp(false, packageName); 363 // Take away CHANGE_WIFI_STATE app-ops from the app. 364 mAppOps.setMode(AppOpsManager.OP_CHANGE_WIFI_STATE, uid, packageName, 365 MODE_IGNORED); 366 break; 367 case NOTIFICATION_USER_DISMISSED_INTENT_ACTION: 368 Log.i(TAG, "User dismissed the notification"); 369 mUserApprovalNotificationActive = false; 370 return; // no need to cancel a dismissed notification, return. 371 default: 372 Log.e(TAG, "Unknown action " + intent.getAction()); 373 return; 374 } 375 // Clear notification once the user interacts with it. 376 mUserApprovalNotificationActive = false; 377 mNotificationManager.cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE); 378 } 379 }; 380 WifiNetworkSuggestionsManager(Context context, Handler handler, WifiInjector wifiInjector, WifiPermissionsUtil wifiPermissionsUtil, WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore, WifiMetrics wifiMetrics, WifiKeyStore keyStore)381 public WifiNetworkSuggestionsManager(Context context, Handler handler, 382 WifiInjector wifiInjector, 383 WifiPermissionsUtil wifiPermissionsUtil, 384 WifiConfigManager wifiConfigManager, 385 WifiConfigStore wifiConfigStore, 386 WifiMetrics wifiMetrics, 387 WifiKeyStore keyStore) { 388 mContext = context; 389 mResources = context.getResources(); 390 mHandler = handler; 391 mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 392 mNotificationManager = 393 (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 394 mPackageManager = context.getPackageManager(); 395 mWifiInjector = wifiInjector; 396 mFrameworkFacade = mWifiInjector.getFrameworkFacade(); 397 mWifiPermissionsUtil = wifiPermissionsUtil; 398 mWifiConfigManager = wifiConfigManager; 399 mWifiMetrics = wifiMetrics; 400 mWifiKeyStore = keyStore; 401 402 // register the data store for serializing/deserializing data. 403 wifiConfigStore.registerStoreData( 404 wifiInjector.makeNetworkSuggestionStoreData(new NetworkSuggestionDataSource())); 405 406 // Register broadcast receiver for UI interactions. 407 mIntentFilter = new IntentFilter(); 408 mIntentFilter.addAction(NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION); 409 mIntentFilter.addAction(NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION); 410 mIntentFilter.addAction(NOTIFICATION_USER_DISMISSED_INTENT_ACTION); 411 mContext.registerReceiver(mBroadcastReceiver, mIntentFilter); 412 } 413 414 /** 415 * Enable verbose logging. 416 */ enableVerboseLogging(int verbose)417 public void enableVerboseLogging(int verbose) { 418 mVerboseLoggingEnabled = verbose > 0; 419 } 420 saveToStore()421 private void saveToStore() { 422 // Set the flag to let WifiConfigStore that we have new data to write. 423 mHasNewDataToSerialize = true; 424 if (!mWifiConfigManager.saveToStore(true)) { 425 Log.w(TAG, "Failed to save to store"); 426 } 427 } 428 addToScanResultMatchInfoMap( @onNull Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions)429 private void addToScanResultMatchInfoMap( 430 @NonNull Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions) { 431 for (ExtendedWifiNetworkSuggestion extNetworkSuggestion : extNetworkSuggestions) { 432 ScanResultMatchInfo scanResultMatchInfo = 433 ScanResultMatchInfo.fromWifiConfiguration( 434 extNetworkSuggestion.wns.wifiConfiguration); 435 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsForScanResultMatchInfo; 436 if (!TextUtils.isEmpty(extNetworkSuggestion.wns.wifiConfiguration.BSSID)) { 437 Pair<ScanResultMatchInfo, MacAddress> lookupPair = 438 Pair.create(scanResultMatchInfo, 439 MacAddress.fromString( 440 extNetworkSuggestion.wns.wifiConfiguration.BSSID)); 441 extNetworkSuggestionsForScanResultMatchInfo = 442 mActiveScanResultMatchInfoWithBssid.get(lookupPair); 443 if (extNetworkSuggestionsForScanResultMatchInfo == null) { 444 extNetworkSuggestionsForScanResultMatchInfo = new HashSet<>(); 445 mActiveScanResultMatchInfoWithBssid.put( 446 lookupPair, extNetworkSuggestionsForScanResultMatchInfo); 447 } 448 } else { 449 extNetworkSuggestionsForScanResultMatchInfo = 450 mActiveScanResultMatchInfoWithNoBssid.get(scanResultMatchInfo); 451 if (extNetworkSuggestionsForScanResultMatchInfo == null) { 452 extNetworkSuggestionsForScanResultMatchInfo = new HashSet<>(); 453 mActiveScanResultMatchInfoWithNoBssid.put( 454 scanResultMatchInfo, extNetworkSuggestionsForScanResultMatchInfo); 455 } 456 } 457 extNetworkSuggestionsForScanResultMatchInfo.add(extNetworkSuggestion); 458 } 459 } 460 removeFromScanResultMatchInfoMap( @onNull Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions)461 private void removeFromScanResultMatchInfoMap( 462 @NonNull Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions) { 463 for (ExtendedWifiNetworkSuggestion extNetworkSuggestion : extNetworkSuggestions) { 464 ScanResultMatchInfo scanResultMatchInfo = 465 ScanResultMatchInfo.fromWifiConfiguration( 466 extNetworkSuggestion.wns.wifiConfiguration); 467 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsForScanResultMatchInfo; 468 if (!TextUtils.isEmpty(extNetworkSuggestion.wns.wifiConfiguration.BSSID)) { 469 Pair<ScanResultMatchInfo, MacAddress> lookupPair = 470 Pair.create(scanResultMatchInfo, 471 MacAddress.fromString( 472 extNetworkSuggestion.wns.wifiConfiguration.BSSID)); 473 extNetworkSuggestionsForScanResultMatchInfo = 474 mActiveScanResultMatchInfoWithBssid.get(lookupPair); 475 // This should never happen because we should have done necessary error checks in 476 // the parent method. 477 if (extNetworkSuggestionsForScanResultMatchInfo == null) { 478 Log.wtf(TAG, "No scan result match info found."); 479 } 480 extNetworkSuggestionsForScanResultMatchInfo.remove(extNetworkSuggestion); 481 // Remove the set from map if empty. 482 if (extNetworkSuggestionsForScanResultMatchInfo.isEmpty()) { 483 mActiveScanResultMatchInfoWithBssid.remove(lookupPair); 484 } 485 } else { 486 extNetworkSuggestionsForScanResultMatchInfo = 487 mActiveScanResultMatchInfoWithNoBssid.get(scanResultMatchInfo); 488 // This should never happen because we should have done necessary error checks in 489 // the parent method. 490 if (extNetworkSuggestionsForScanResultMatchInfo == null) { 491 Log.wtf(TAG, "No scan result match info found."); 492 } 493 extNetworkSuggestionsForScanResultMatchInfo.remove(extNetworkSuggestion); 494 // Remove the set from map if empty. 495 if (extNetworkSuggestionsForScanResultMatchInfo.isEmpty()) { 496 mActiveScanResultMatchInfoWithNoBssid.remove(scanResultMatchInfo); 497 } 498 } 499 } 500 } 501 502 // Issues a disconnect if the only serving network suggestion is removed. 503 // TODO (b/115504887): What if there is also a saved network with the same credentials? triggerDisconnectIfServingNetworkSuggestionRemoved( Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsRemoved)504 private void triggerDisconnectIfServingNetworkSuggestionRemoved( 505 Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsRemoved) { 506 if (mActiveNetworkSuggestionsMatchingConnection == null 507 || mActiveNetworkSuggestionsMatchingConnection.isEmpty()) { 508 return; 509 } 510 if (mActiveNetworkSuggestionsMatchingConnection.removeAll(extNetworkSuggestionsRemoved)) { 511 if (mActiveNetworkSuggestionsMatchingConnection.isEmpty()) { 512 Log.i(TAG, "Only network suggestion matching the connected network removed. " 513 + "Disconnecting..."); 514 mWifiInjector.getClientModeImpl().disconnectCommand(); 515 } 516 } 517 } 518 startTrackingAppOpsChange(@onNull String packageName, int uid)519 private void startTrackingAppOpsChange(@NonNull String packageName, int uid) { 520 AppOpsChangedListener appOpsChangedListener = 521 new AppOpsChangedListener(packageName, uid); 522 mAppOps.startWatchingMode(OPSTR_CHANGE_WIFI_STATE, packageName, appOpsChangedListener); 523 mAppOpsChangedListenerPerApp.put(packageName, appOpsChangedListener); 524 } 525 526 /** 527 * Helper method to convert the incoming collection of public {@link WifiNetworkSuggestion} 528 * objects to a set of corresponding internal wrapper 529 * {@link ExtendedWifiNetworkSuggestion} objects. 530 */ convertToExtendedWnsSet( final Collection<WifiNetworkSuggestion> networkSuggestions, final PerAppInfo perAppInfo)531 private Set<ExtendedWifiNetworkSuggestion> convertToExtendedWnsSet( 532 final Collection<WifiNetworkSuggestion> networkSuggestions, 533 final PerAppInfo perAppInfo) { 534 return networkSuggestions 535 .stream() 536 .collect(Collectors.mapping( 537 n -> ExtendedWifiNetworkSuggestion.fromWns(n, perAppInfo), 538 Collectors.toSet())); 539 } 540 541 /** 542 * Helper method to convert the incoming collection of internal wrapper 543 * {@link ExtendedWifiNetworkSuggestion} objects to a set of corresponding public 544 * {@link WifiNetworkSuggestion} objects. 545 */ convertToWnsSet( final Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions)546 private Set<WifiNetworkSuggestion> convertToWnsSet( 547 final Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions) { 548 return extNetworkSuggestions 549 .stream() 550 .collect(Collectors.mapping( 551 n -> n.wns, 552 Collectors.toSet())); 553 } 554 555 /** 556 * Add the provided list of network suggestions from the corresponding app's active list. 557 */ add( List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName)558 public @WifiManager.NetworkSuggestionsStatusCode int add( 559 List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName) { 560 if (mVerboseLoggingEnabled) { 561 Log.v(TAG, "Adding " + networkSuggestions.size() + " networks from " + packageName); 562 } 563 if (networkSuggestions.isEmpty()) { 564 Log.w(TAG, "Empty list of network suggestions for " + packageName + ". Ignoring"); 565 return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; 566 } 567 PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName); 568 if (perAppInfo == null) { 569 perAppInfo = new PerAppInfo(packageName); 570 mActiveNetworkSuggestionsPerApp.put(packageName, perAppInfo); 571 if (mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(uid)) { 572 Log.i(TAG, "Setting the carrier provisioning app approved"); 573 perAppInfo.hasUserApproved = true; 574 } 575 } 576 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = 577 convertToExtendedWnsSet(networkSuggestions, perAppInfo); 578 // check if the app is trying to in-place modify network suggestions. 579 if (!Collections.disjoint(perAppInfo.extNetworkSuggestions, extNetworkSuggestions)) { 580 Log.e(TAG, "Failed to add network suggestions for " + packageName 581 + ". Modification of active network suggestions disallowed"); 582 return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE; 583 } 584 if (perAppInfo.extNetworkSuggestions.size() + extNetworkSuggestions.size() 585 > WifiManager.NETWORK_SUGGESTIONS_MAX_PER_APP) { 586 Log.e(TAG, "Failed to add network suggestions for " + packageName 587 + ". Exceeds max per app, current list size: " 588 + perAppInfo.extNetworkSuggestions.size() 589 + ", new list size: " 590 + extNetworkSuggestions.size()); 591 return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP; 592 } 593 if (perAppInfo.extNetworkSuggestions.isEmpty()) { 594 // Start tracking app-op changes from the app if they have active suggestions. 595 startTrackingAppOpsChange(packageName, uid); 596 } 597 Iterator<ExtendedWifiNetworkSuggestion> iterator = extNetworkSuggestions.iterator(); 598 // Install enterprise network suggestion catificate. 599 while (iterator.hasNext()) { 600 WifiConfiguration config = iterator.next().wns.wifiConfiguration; 601 if (!config.isEnterprise()) { 602 continue; 603 } 604 if (!mWifiKeyStore.updateNetworkKeys(config, null)) { 605 Log.e(TAG, "Enterprise network install failure for SSID: " 606 + config.SSID); 607 iterator.remove(); 608 } 609 } 610 perAppInfo.extNetworkSuggestions.addAll(extNetworkSuggestions); 611 // Update the max size for this app. 612 perAppInfo.maxSize = Math.max(perAppInfo.extNetworkSuggestions.size(), perAppInfo.maxSize); 613 addToScanResultMatchInfoMap(extNetworkSuggestions); 614 saveToStore(); 615 mWifiMetrics.incrementNetworkSuggestionApiNumModification(); 616 mWifiMetrics.noteNetworkSuggestionApiListSizeHistogram(getAllMaxSizes()); 617 return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; 618 } 619 stopTrackingAppOpsChange(@onNull String packageName)620 private void stopTrackingAppOpsChange(@NonNull String packageName) { 621 AppOpsChangedListener appOpsChangedListener = 622 mAppOpsChangedListenerPerApp.remove(packageName); 623 if (appOpsChangedListener == null) { 624 Log.wtf(TAG, "No app ops listener found for " + packageName); 625 return; 626 } 627 mAppOps.stopWatchingMode(appOpsChangedListener); 628 } 629 removeInternal( @onNull Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions, @NonNull String packageName, @NonNull PerAppInfo perAppInfo)630 private void removeInternal( 631 @NonNull Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions, 632 @NonNull String packageName, 633 @NonNull PerAppInfo perAppInfo) { 634 // Get internal suggestions 635 Set<ExtendedWifiNetworkSuggestion> removingSuggestions = 636 new HashSet<>(perAppInfo.extNetworkSuggestions); 637 if (!extNetworkSuggestions.isEmpty()) { 638 // Keep the internal suggestions need to remove. 639 removingSuggestions.retainAll(extNetworkSuggestions); 640 perAppInfo.extNetworkSuggestions.removeAll(extNetworkSuggestions); 641 } else { 642 // empty list is used to clear everything for the app. Store a copy for use below. 643 extNetworkSuggestions = new HashSet<>(perAppInfo.extNetworkSuggestions); 644 perAppInfo.extNetworkSuggestions.clear(); 645 } 646 if (perAppInfo.extNetworkSuggestions.isEmpty()) { 647 // Note: We don't remove the app entry even if there is no active suggestions because 648 // we want to keep the notification state for all apps that have ever provided 649 // suggestions. 650 if (mVerboseLoggingEnabled) Log.v(TAG, "No active suggestions for " + packageName); 651 // Stop tracking app-op changes from the app if they don't have active suggestions. 652 stopTrackingAppOpsChange(packageName); 653 } 654 // Clean the enterprise certifiacte. 655 for (ExtendedWifiNetworkSuggestion ewns : removingSuggestions) { 656 WifiConfiguration config = ewns.wns.wifiConfiguration; 657 if (!config.isEnterprise()) { 658 continue; 659 } 660 mWifiKeyStore.removeKeys(config.enterpriseConfig); 661 } 662 // Clear the scan cache. 663 removeFromScanResultMatchInfoMap(removingSuggestions); 664 } 665 666 /** 667 * Remove the provided list of network suggestions from the corresponding app's active list. 668 */ remove( List<WifiNetworkSuggestion> networkSuggestions, String packageName)669 public @WifiManager.NetworkSuggestionsStatusCode int remove( 670 List<WifiNetworkSuggestion> networkSuggestions, String packageName) { 671 if (mVerboseLoggingEnabled) { 672 Log.v(TAG, "Removing " + networkSuggestions.size() + " networks from " + packageName); 673 } 674 PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName); 675 if (perAppInfo == null) { 676 Log.e(TAG, "Failed to remove network suggestions for " + packageName 677 + ". No network suggestions found"); 678 return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID; 679 } 680 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = 681 convertToExtendedWnsSet(networkSuggestions, perAppInfo); 682 // check if all the request network suggestions are present in the active list. 683 if (!extNetworkSuggestions.isEmpty() 684 && !perAppInfo.extNetworkSuggestions.containsAll(extNetworkSuggestions)) { 685 Log.e(TAG, "Failed to remove network suggestions for " + packageName 686 + ". Network suggestions not found in active network suggestions"); 687 return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID; 688 } 689 removeInternal(extNetworkSuggestions, packageName, perAppInfo); 690 saveToStore(); 691 mWifiMetrics.incrementNetworkSuggestionApiNumModification(); 692 mWifiMetrics.noteNetworkSuggestionApiListSizeHistogram(getAllMaxSizes()); 693 return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; 694 } 695 696 /** 697 * Remove all tracking of the app that has been uninstalled. 698 */ removeApp(@onNull String packageName)699 public void removeApp(@NonNull String packageName) { 700 PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName); 701 if (perAppInfo == null) return; 702 // Disconnect from the current network, if the only suggestion for it was removed. 703 triggerDisconnectIfServingNetworkSuggestionRemoved(perAppInfo.extNetworkSuggestions); 704 removeInternal(Collections.EMPTY_LIST, packageName, perAppInfo); 705 // Remove the package fully from the internal database. 706 mActiveNetworkSuggestionsPerApp.remove(packageName); 707 saveToStore(); 708 Log.i(TAG, "Removed " + packageName); 709 } 710 711 /** 712 * Clear all internal state (for network settings reset). 713 */ clear()714 public void clear() { 715 Iterator<Map.Entry<String, PerAppInfo>> iter = 716 mActiveNetworkSuggestionsPerApp.entrySet().iterator(); 717 // Disconnect if we're connected to one of the suggestions. 718 triggerDisconnectIfServingNetworkSuggestionRemoved( 719 mActiveNetworkSuggestionsMatchingConnection); 720 while (iter.hasNext()) { 721 Map.Entry<String, PerAppInfo> entry = iter.next(); 722 removeInternal(Collections.EMPTY_LIST, entry.getKey(), entry.getValue()); 723 iter.remove(); 724 } 725 saveToStore(); 726 Log.i(TAG, "Cleared all internal state"); 727 } 728 729 /** 730 * Check if network suggestions are enabled or disabled for the app. 731 */ hasUserApprovedForApp(String packageName)732 public boolean hasUserApprovedForApp(String packageName) { 733 PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName); 734 if (perAppInfo == null) return false; 735 736 return perAppInfo.hasUserApproved; 737 } 738 739 /** 740 * Enable or Disable network suggestions for the app. 741 */ setHasUserApprovedForApp(boolean approved, String packageName)742 public void setHasUserApprovedForApp(boolean approved, String packageName) { 743 PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName); 744 if (perAppInfo == null) return; 745 746 if (mVerboseLoggingEnabled) { 747 Log.v(TAG, "Setting the app " + (approved ? "approved" : "not approved")); 748 } 749 perAppInfo.hasUserApproved = approved; 750 saveToStore(); 751 } 752 753 /** 754 * Returns a set of all network suggestions across all apps. 755 */ 756 @VisibleForTesting getAllNetworkSuggestions()757 public Set<WifiNetworkSuggestion> getAllNetworkSuggestions() { 758 return mActiveNetworkSuggestionsPerApp.values() 759 .stream() 760 .flatMap(e -> convertToWnsSet(e.extNetworkSuggestions) 761 .stream()) 762 .collect(Collectors.toSet()); 763 } 764 getAllMaxSizes()765 private List<Integer> getAllMaxSizes() { 766 return mActiveNetworkSuggestionsPerApp.values() 767 .stream() 768 .map(e -> e.maxSize) 769 .collect(Collectors.toList()); 770 } 771 getPrivateBroadcast(@onNull String action, @NonNull String packageName, int uid)772 private PendingIntent getPrivateBroadcast(@NonNull String action, @NonNull String packageName, 773 int uid) { 774 Intent intent = new Intent(action) 775 .setPackage("android") 776 .putExtra(EXTRA_PACKAGE_NAME, packageName) 777 .putExtra(EXTRA_UID, uid); 778 return mFrameworkFacade.getBroadcast(mContext, 0, intent, 779 PendingIntent.FLAG_UPDATE_CURRENT); 780 } 781 getAppName(@onNull String packageName)782 private @NonNull CharSequence getAppName(@NonNull String packageName) { 783 ApplicationInfo applicationInfo = null; 784 try { 785 applicationInfo = mPackageManager.getApplicationInfo(packageName, 0); 786 } catch (PackageManager.NameNotFoundException e) { 787 Log.e(TAG, "Failed to find app name for " + packageName); 788 return ""; 789 } 790 CharSequence appName = mPackageManager.getApplicationLabel(applicationInfo); 791 return (appName != null) ? appName : ""; 792 } 793 sendUserApprovalNotification(@onNull String packageName, int uid)794 private void sendUserApprovalNotification(@NonNull String packageName, int uid) { 795 Notification.Action userAllowAppNotificationAction = 796 new Notification.Action.Builder(null, 797 mResources.getText(R.string.wifi_suggestion_action_allow_app), 798 getPrivateBroadcast(NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION, 799 packageName, uid)) 800 .build(); 801 Notification.Action userDisallowAppNotificationAction = 802 new Notification.Action.Builder(null, 803 mResources.getText(R.string.wifi_suggestion_action_disallow_app), 804 getPrivateBroadcast(NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION, 805 packageName, uid)) 806 .build(); 807 808 CharSequence appName = getAppName(packageName); 809 Notification notification = new Notification.Builder( 810 mContext, SystemNotificationChannels.NETWORK_STATUS) 811 .setSmallIcon(R.drawable.stat_notify_wifi_in_range) 812 .setTicker(mResources.getString(R.string.wifi_suggestion_title)) 813 .setContentTitle(mResources.getString(R.string.wifi_suggestion_title)) 814 .setContentText(mResources.getString(R.string.wifi_suggestion_content, appName)) 815 .setDeleteIntent(getPrivateBroadcast(NOTIFICATION_USER_DISMISSED_INTENT_ACTION, 816 packageName, uid)) 817 .setShowWhen(false) 818 .setLocalOnly(true) 819 .setColor(mResources.getColor(R.color.system_notification_accent_color, 820 mContext.getTheme())) 821 .addAction(userAllowAppNotificationAction) 822 .addAction(userDisallowAppNotificationAction) 823 .build(); 824 825 // Post the notification. 826 mNotificationManager.notify( 827 SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE, notification); 828 mUserApprovalNotificationActive = true; 829 mUserApprovalNotificationPackageName = packageName; 830 } 831 sendUserApprovalNotificationIfNotApproved( @onNull PerAppInfo perAppInfo, @NonNull WifiNetworkSuggestion matchingSuggestion)832 private boolean sendUserApprovalNotificationIfNotApproved( 833 @NonNull PerAppInfo perAppInfo, 834 @NonNull WifiNetworkSuggestion matchingSuggestion) { 835 if (perAppInfo.hasUserApproved) { 836 return false; // already approved. 837 } 838 839 Log.i(TAG, "Sending user approval notification for " + perAppInfo.packageName); 840 sendUserApprovalNotification(perAppInfo.packageName, matchingSuggestion.suggestorUid); 841 return true; 842 } 843 844 private @Nullable Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForScanResultMatchInfo( @onNull ScanResultMatchInfo scanResultMatchInfo, @Nullable MacAddress bssid)845 getNetworkSuggestionsForScanResultMatchInfo( 846 @NonNull ScanResultMatchInfo scanResultMatchInfo, @Nullable MacAddress bssid) { 847 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = new HashSet<>(); 848 if (bssid != null) { 849 Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsWithBssid = 850 mActiveScanResultMatchInfoWithBssid.get( 851 Pair.create(scanResultMatchInfo, bssid)); 852 if (matchingExtNetworkSuggestionsWithBssid != null) { 853 extNetworkSuggestions.addAll(matchingExtNetworkSuggestionsWithBssid); 854 } 855 } 856 Set<ExtendedWifiNetworkSuggestion> matchingNetworkSuggestionsWithNoBssid = 857 mActiveScanResultMatchInfoWithNoBssid.get(scanResultMatchInfo); 858 if (matchingNetworkSuggestionsWithNoBssid != null) { 859 extNetworkSuggestions.addAll(matchingNetworkSuggestionsWithNoBssid); 860 } 861 if (extNetworkSuggestions.isEmpty()) { 862 return null; 863 } 864 return extNetworkSuggestions; 865 } 866 867 /** 868 * Returns a set of all network suggestions matching the provided scan detail. 869 */ getNetworkSuggestionsForScanDetail( @onNull ScanDetail scanDetail)870 public @Nullable Set<WifiNetworkSuggestion> getNetworkSuggestionsForScanDetail( 871 @NonNull ScanDetail scanDetail) { 872 ScanResult scanResult = scanDetail.getScanResult(); 873 if (scanResult == null) { 874 Log.e(TAG, "No scan result found in scan detail"); 875 return null; 876 } 877 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = null; 878 try { 879 ScanResultMatchInfo scanResultMatchInfo = 880 ScanResultMatchInfo.fromScanResult(scanResult); 881 extNetworkSuggestions = getNetworkSuggestionsForScanResultMatchInfo( 882 scanResultMatchInfo, MacAddress.fromString(scanResult.BSSID)); 883 } catch (IllegalArgumentException e) { 884 Log.e(TAG, "Failed to lookup network from scan result match info map", e); 885 } 886 if (extNetworkSuggestions == null) { 887 return null; 888 } 889 Set<ExtendedWifiNetworkSuggestion> approvedExtNetworkSuggestions = 890 extNetworkSuggestions 891 .stream() 892 .filter(n -> n.perAppInfo.hasUserApproved) 893 .collect(Collectors.toSet()); 894 // If there is no active notification, check if we need to get approval for any of the apps 895 // & send a notification for one of them. If there are multiple packages awaiting approval, 896 // we end up picking the first one. The others will be reconsidered in the next iteration. 897 if (!mUserApprovalNotificationActive 898 && approvedExtNetworkSuggestions.size() != extNetworkSuggestions.size()) { 899 for (ExtendedWifiNetworkSuggestion extNetworkSuggestion : extNetworkSuggestions) { 900 if (sendUserApprovalNotificationIfNotApproved( 901 extNetworkSuggestion.perAppInfo, extNetworkSuggestion.wns)) { 902 break; 903 } 904 } 905 } 906 if (approvedExtNetworkSuggestions.isEmpty()) { 907 return null; 908 } 909 if (mVerboseLoggingEnabled) { 910 Log.v(TAG, "getNetworkSuggestionsForScanDetail Found " 911 + approvedExtNetworkSuggestions + " for " + scanResult.SSID 912 + "[" + scanResult.capabilities + "]"); 913 } 914 return convertToWnsSet(approvedExtNetworkSuggestions); 915 } 916 917 /** 918 * Returns a set of all network suggestions matching the provided the WifiConfiguration. 919 */ getNetworkSuggestionsForWifiConfiguration( @onNull WifiConfiguration wifiConfiguration, @Nullable String bssid)920 private @Nullable Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForWifiConfiguration( 921 @NonNull WifiConfiguration wifiConfiguration, @Nullable String bssid) { 922 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = null; 923 try { 924 ScanResultMatchInfo scanResultMatchInfo = 925 ScanResultMatchInfo.fromWifiConfiguration(wifiConfiguration); 926 extNetworkSuggestions = getNetworkSuggestionsForScanResultMatchInfo( 927 scanResultMatchInfo, bssid == null ? null : MacAddress.fromString(bssid)); 928 } catch (IllegalArgumentException e) { 929 Log.e(TAG, "Failed to lookup network from scan result match info map", e); 930 } 931 if (extNetworkSuggestions == null) { 932 return null; 933 } 934 Set<ExtendedWifiNetworkSuggestion> approvedExtNetworkSuggestions = 935 extNetworkSuggestions 936 .stream() 937 .filter(n -> n.perAppInfo.hasUserApproved) 938 .collect(Collectors.toSet()); 939 if (approvedExtNetworkSuggestions.isEmpty()) { 940 return null; 941 } 942 if (mVerboseLoggingEnabled) { 943 Log.v(TAG, "getNetworkSuggestionsFoWifiConfiguration Found " 944 + approvedExtNetworkSuggestions + " for " + wifiConfiguration.SSID 945 + "[" + wifiConfiguration.allowedKeyManagement + "]"); 946 } 947 return approvedExtNetworkSuggestions; 948 } 949 950 /** 951 * Helper method to send the post connection broadcast to specified package. 952 */ sendPostConnectionBroadcast( String packageName, WifiNetworkSuggestion networkSuggestion)953 private void sendPostConnectionBroadcast( 954 String packageName, WifiNetworkSuggestion networkSuggestion) { 955 Intent intent = new Intent(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION); 956 intent.putExtra(WifiManager.EXTRA_NETWORK_SUGGESTION, networkSuggestion); 957 // Intended to wakeup the receiving app so set the specific package name. 958 intent.setPackage(packageName); 959 mContext.sendBroadcastAsUser( 960 intent, UserHandle.getUserHandleForUid(networkSuggestion.suggestorUid)); 961 } 962 963 /** 964 * Helper method to send the post connection broadcast to specified package. 965 */ sendPostConnectionBroadcastIfAllowed( String packageName, WifiNetworkSuggestion matchingSuggestion)966 private void sendPostConnectionBroadcastIfAllowed( 967 String packageName, WifiNetworkSuggestion matchingSuggestion) { 968 try { 969 mWifiPermissionsUtil.enforceCanAccessScanResults( 970 packageName, matchingSuggestion.suggestorUid); 971 } catch (SecurityException se) { 972 return; 973 } 974 if (mVerboseLoggingEnabled) { 975 Log.v(TAG, "Sending post connection broadcast to " + packageName); 976 } 977 sendPostConnectionBroadcast(packageName, matchingSuggestion); 978 } 979 980 /** 981 * Send out the {@link WifiManager#ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} to all the 982 * network suggestion credentials that match the current connection network. 983 * 984 * @param connectedNetwork {@link WifiConfiguration} representing the network connected to. 985 * @param connectedBssid BSSID of the network connected to. 986 */ handleConnectionSuccess( @onNull WifiConfiguration connectedNetwork, @NonNull String connectedBssid)987 private void handleConnectionSuccess( 988 @NonNull WifiConfiguration connectedNetwork, @NonNull String connectedBssid) { 989 Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions = 990 getNetworkSuggestionsForWifiConfiguration(connectedNetwork, connectedBssid); 991 if (mVerboseLoggingEnabled) { 992 Log.v(TAG, "Network suggestions matching the connection " 993 + matchingExtNetworkSuggestions); 994 } 995 if (matchingExtNetworkSuggestions == null 996 || matchingExtNetworkSuggestions.isEmpty()) return; 997 998 mWifiMetrics.incrementNetworkSuggestionApiNumConnectSuccess(); 999 1000 // Store the set of matching network suggestions. 1001 mActiveNetworkSuggestionsMatchingConnection = new HashSet<>(matchingExtNetworkSuggestions); 1002 1003 // Find subset of network suggestions which have set |isAppInteractionRequired|. 1004 Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsWithReqAppInteraction = 1005 matchingExtNetworkSuggestions.stream() 1006 .filter(x -> x.wns.isAppInteractionRequired) 1007 .collect(Collectors.toSet()); 1008 if (matchingExtNetworkSuggestionsWithReqAppInteraction.size() == 0) return; 1009 1010 // Iterate over the matching network suggestions list: 1011 // a) Ensure that these apps have the necessary location permissions. 1012 // b) Send directed broadcast to the app with their corresponding network suggestion. 1013 for (ExtendedWifiNetworkSuggestion matchingExtNetworkSuggestion 1014 : matchingExtNetworkSuggestionsWithReqAppInteraction) { 1015 sendPostConnectionBroadcastIfAllowed( 1016 matchingExtNetworkSuggestion.perAppInfo.packageName, 1017 matchingExtNetworkSuggestion.wns); 1018 } 1019 } 1020 1021 /** 1022 * Handle connection failure. 1023 * 1024 * @param network {@link WifiConfiguration} representing the network that connection failed to. 1025 * @param bssid BSSID of the network connection failed to if known, else null. 1026 */ handleConnectionFailure(@onNull WifiConfiguration network, @Nullable String bssid)1027 private void handleConnectionFailure(@NonNull WifiConfiguration network, 1028 @Nullable String bssid) { 1029 Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions = 1030 getNetworkSuggestionsForWifiConfiguration(network, bssid); 1031 if (mVerboseLoggingEnabled) { 1032 Log.v(TAG, "Network suggestions matching the connection failure " 1033 + matchingExtNetworkSuggestions); 1034 } 1035 if (matchingExtNetworkSuggestions == null 1036 || matchingExtNetworkSuggestions.isEmpty()) return; 1037 1038 mWifiMetrics.incrementNetworkSuggestionApiNumConnectFailure(); 1039 // TODO (b/115504887, b/112196799): Blacklist the corresponding network suggestion if 1040 // the connection failed. 1041 } 1042 resetConnectionState()1043 private void resetConnectionState() { 1044 mActiveNetworkSuggestionsMatchingConnection = null; 1045 } 1046 1047 /** 1048 * Invoked by {@link ClientModeImpl} on end of connection attempt to a network. 1049 * 1050 * @param failureCode Failure codes representing {@link WifiMetrics.ConnectionEvent} codes. 1051 * @param network WifiConfiguration corresponding to the current network. 1052 * @param bssid BSSID of the current network. 1053 */ handleConnectionAttemptEnded( int failureCode, @NonNull WifiConfiguration network, @Nullable String bssid)1054 public void handleConnectionAttemptEnded( 1055 int failureCode, @NonNull WifiConfiguration network, @Nullable String bssid) { 1056 if (mVerboseLoggingEnabled) { 1057 Log.v(TAG, "handleConnectionAttemptEnded " + failureCode + ", " + network); 1058 } 1059 resetConnectionState(); 1060 if (failureCode == WifiMetrics.ConnectionEvent.FAILURE_NONE) { 1061 handleConnectionSuccess(network, bssid); 1062 } else { 1063 handleConnectionFailure(network, bssid); 1064 } 1065 } 1066 1067 /** 1068 * Invoked by {@link ClientModeImpl} on disconnect from network. 1069 */ handleDisconnect(@onNull WifiConfiguration network, @NonNull String bssid)1070 public void handleDisconnect(@NonNull WifiConfiguration network, @NonNull String bssid) { 1071 if (mVerboseLoggingEnabled) { 1072 Log.v(TAG, "handleDisconnect " + network); 1073 } 1074 resetConnectionState(); 1075 } 1076 1077 /** 1078 * Dump of {@link WifiNetworkSuggestionsManager}. 1079 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)1080 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1081 pw.println("Dump of WifiNetworkSuggestionsManager"); 1082 pw.println("WifiNetworkSuggestionsManager - Networks Begin ----"); 1083 for (Map.Entry<String, PerAppInfo> networkSuggestionsEntry 1084 : mActiveNetworkSuggestionsPerApp.entrySet()) { 1085 pw.println("Package Name: " + networkSuggestionsEntry.getKey()); 1086 PerAppInfo appInfo = networkSuggestionsEntry.getValue(); 1087 pw.println("Has user approved: " + appInfo.hasUserApproved); 1088 for (ExtendedWifiNetworkSuggestion extNetworkSuggestion 1089 : appInfo.extNetworkSuggestions) { 1090 pw.println("Network: " + extNetworkSuggestion); 1091 } 1092 } 1093 pw.println("WifiNetworkSuggestionsManager - Networks End ----"); 1094 pw.println("WifiNetworkSuggestionsManager - Network Suggestions matching connection: " 1095 + mActiveNetworkSuggestionsMatchingConnection); 1096 } 1097 } 1098 1099