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