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 import static android.net.wifi.WifiManager.ACTION_REMOVE_SUGGESTION_DISCONNECT; 22 import static android.net.wifi.WifiManager.ACTION_REMOVE_SUGGESTION_LINGER; 23 24 import android.annotation.IntDef; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.annotation.SuppressLint; 28 import android.app.ActivityManager; 29 import android.app.AppOpsManager; 30 import android.app.Notification; 31 import android.app.PendingIntent; 32 import android.content.BroadcastReceiver; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.IntentFilter; 36 import android.content.res.Resources; 37 import android.graphics.drawable.Icon; 38 import android.net.MacAddress; 39 import android.net.wifi.ISuggestionConnectionStatusListener; 40 import android.net.wifi.ISuggestionUserApprovalStatusListener; 41 import android.net.wifi.ScanResult; 42 import android.net.wifi.SecurityParams; 43 import android.net.wifi.WifiConfiguration; 44 import android.net.wifi.WifiContext; 45 import android.net.wifi.WifiEnterpriseConfig; 46 import android.net.wifi.WifiManager; 47 import android.net.wifi.WifiNetworkSuggestion; 48 import android.net.wifi.WifiScanner; 49 import android.net.wifi.hotspot2.PasspointConfiguration; 50 import android.os.Handler; 51 import android.os.Process; 52 import android.os.RemoteCallbackList; 53 import android.os.RemoteException; 54 import android.os.SystemProperties; 55 import android.os.UserHandle; 56 import android.telephony.SubscriptionManager; 57 import android.telephony.TelephonyManager; 58 import android.text.TextUtils; 59 import android.util.ArrayMap; 60 import android.util.ArraySet; 61 import android.util.EventLog; 62 import android.util.Log; 63 import android.util.Pair; 64 65 import com.android.internal.annotations.VisibleForTesting; 66 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; 67 import com.android.modules.utils.build.SdkLevel; 68 import com.android.server.wifi.util.LruConnectionTracker; 69 import com.android.server.wifi.util.WifiPermissionsUtil; 70 import com.android.wifi.resources.R; 71 72 import java.io.FileDescriptor; 73 import java.io.PrintWriter; 74 import java.lang.annotation.Retention; 75 import java.lang.annotation.RetentionPolicy; 76 import java.util.ArrayList; 77 import java.util.Collection; 78 import java.util.Comparator; 79 import java.util.HashMap; 80 import java.util.HashSet; 81 import java.util.Iterator; 82 import java.util.List; 83 import java.util.Map; 84 import java.util.Objects; 85 import java.util.Optional; 86 import java.util.Set; 87 import java.util.stream.Collectors; 88 89 import javax.annotation.concurrent.NotThreadSafe; 90 91 /** 92 * Network Suggestions Manager. 93 * NOTE: This class should always be invoked from the main wifi service thread. 94 */ 95 @NotThreadSafe 96 @SuppressLint("LongLogTag") 97 public class WifiNetworkSuggestionsManager { 98 private static final String TAG = "WifiNetworkSuggestionsManager"; 99 100 /** Intent when user tapped action button to allow the app. */ 101 @VisibleForTesting 102 public static final String NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION = 103 "com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_APP"; 104 /** Intent when user tapped action button to disallow the app. */ 105 @VisibleForTesting 106 public static final String NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION = 107 "com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_APP"; 108 /** Intent when user dismissed the notification. */ 109 @VisibleForTesting 110 public static final String NOTIFICATION_USER_DISMISSED_INTENT_ACTION = 111 "com.android.server.wifi.action.NetworkSuggestion.USER_DISMISSED"; 112 @VisibleForTesting 113 public static final String EXTRA_PACKAGE_NAME = 114 "com.android.server.wifi.extra.NetworkSuggestion.PACKAGE_NAME"; 115 @VisibleForTesting 116 public static final String EXTRA_UID = 117 "com.android.server.wifi.extra.NetworkSuggestion.UID"; 118 119 public static final int APP_TYPE_CARRIER_PRIVILEGED = 1; 120 public static final int APP_TYPE_NETWORK_PROVISIONING = 2; 121 public static final int APP_TYPE_NON_PRIVILEGED = 3; 122 123 public static final int ACTION_USER_ALLOWED_APP = 1; 124 public static final int ACTION_USER_DISALLOWED_APP = 2; 125 public static final int ACTION_USER_DISMISS = 3; 126 127 public static final int DEFAULT_PRIORITY_GROUP = 0; 128 129 @IntDef(prefix = { "ACTION_USER_" }, value = { 130 ACTION_USER_ALLOWED_APP, 131 ACTION_USER_DISALLOWED_APP, 132 ACTION_USER_DISMISS 133 }) 134 @Retention(RetentionPolicy.SOURCE) 135 public @interface UserActionCode { } 136 137 /** 138 * Limit number of hidden networks attach to scan 139 */ 140 private static final int NUMBER_OF_HIDDEN_NETWORK_FOR_ONE_SCAN = 100; 141 /** 142 * Expiration timeout for user notification in milliseconds. (15 min) 143 */ 144 private static final long NOTIFICATION_EXPIRY_MILLS = 15 * 60 * 1000; 145 /** 146 * Notification update delay in milliseconds. (10 min) 147 */ 148 private static final long NOTIFICATION_UPDATE_DELAY_MILLS = 10 * 60 * 1000; 149 150 /** 151 * Modifiable only for testing. 152 */ 153 private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger"; 154 /** 155 * Default to 30s linger time-out. Should be same as ConnectivityService#DEFAULT_LINGER_DELAY_MS 156 */ 157 @VisibleForTesting 158 public static final int DEFAULT_LINGER_DELAY_MS = 30_000; 159 160 private final WifiContext mContext; 161 private final Resources mResources; 162 private final Handler mHandler; 163 private final AppOpsManager mAppOps; 164 private final ActivityManager mActivityManager; 165 private final WifiNotificationManager mNotificationManager; 166 private final WifiPermissionsUtil mWifiPermissionsUtil; 167 private final WifiConfigManager mWifiConfigManager; 168 private final WifiMetrics mWifiMetrics; 169 private final WifiInjector mWifiInjector; 170 private final FrameworkFacade mFrameworkFacade; 171 private final WifiCarrierInfoManager mWifiCarrierInfoManager; 172 private final WifiKeyStore mWifiKeyStore; 173 private final Clock mClock; 174 // Keep order of network connection. 175 private final LruConnectionTracker mLruConnectionTracker; 176 177 private class OnNetworkUpdateListener implements 178 WifiConfigManager.OnNetworkUpdateListener { 179 180 @Override onConnectChoiceSet(@onNull List<WifiConfiguration> networks, String choiceKey, int rssi)181 public void onConnectChoiceSet(@NonNull List<WifiConfiguration> networks, 182 String choiceKey, int rssi) { 183 onUserConnectChoiceSetForSuggestion(networks, choiceKey, rssi); 184 } 185 186 @Override onConnectChoiceRemoved(String choiceKey)187 public void onConnectChoiceRemoved(String choiceKey) { 188 onUserConnectChoiceRemoveForSuggestion(choiceKey); 189 } 190 191 @Override onSecurityParamsUpdate(@onNull WifiConfiguration configuration, @NonNull List<SecurityParams> securityParams)192 public void onSecurityParamsUpdate(@NonNull WifiConfiguration configuration, 193 @NonNull List<SecurityParams> securityParams) { 194 if (configuration == null || securityParams == null || securityParams.isEmpty()) { 195 Log.e(TAG, "onSecurityParamsUpdate: must have valid config and " 196 + "securityParams"); 197 return; 198 } 199 onSecurityParamsUpdateForSuggestion(configuration, securityParams); 200 } 201 } 202 203 /** 204 * Per app meta data to store network suggestions, status, etc for each app providing network 205 * suggestions on the device. 206 */ 207 public static class PerAppInfo { 208 /** 209 * UID of the app. 210 */ 211 public int uid; 212 /** 213 * Package Name of the app. 214 */ 215 public final String packageName; 216 /** 217 * First Feature in the package that registered the suggestion 218 */ 219 public final String featureId; 220 /** 221 97 * Map of active network suggestions provided by the app keyed by hashcode. 222 */ 223 public final Map<Integer, ExtendedWifiNetworkSuggestion> extNetworkSuggestions = 224 new ArrayMap<>(); 225 /** 226 * Whether we have shown the user a notification for this app. 227 */ 228 public boolean hasUserApproved = false; 229 /** 230 * Carrier Id of SIM which give app carrier privileges. 231 */ 232 public int carrierId = TelephonyManager.UNKNOWN_CARRIER_ID; 233 234 /** Stores the max size of the {@link #extNetworkSuggestions} list ever for this app */ 235 public int maxSize = 0; 236 PerAppInfo(int uid, @NonNull String packageName, @Nullable String featureId)237 public PerAppInfo(int uid, @NonNull String packageName, @Nullable String featureId) { 238 this.uid = uid; 239 this.packageName = packageName; 240 this.featureId = featureId; 241 } 242 243 /** 244 * Needed for migration of config store data. 245 */ setUid(int uid)246 public void setUid(int uid) { 247 if (this.uid == Process.INVALID_UID) { 248 this.uid = uid; 249 } 250 // else ignored. 251 } 252 253 /** 254 * Needed when a normal App became carrier privileged when SIM insert 255 */ setCarrierId(int carrierId)256 public void setCarrierId(int carrierId) { 257 if (this.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) { 258 this.carrierId = carrierId; 259 } 260 // else ignored. 261 } 262 263 /** 264 * Returns true if this app has the necessary approvals to place network suggestions. 265 */ isApproved()266 private boolean isApproved() { 267 return hasUserApproved || carrierId != TelephonyManager.UNKNOWN_CARRIER_ID; 268 } 269 270 // This is only needed for comparison in unit tests. 271 @Override equals(Object other)272 public boolean equals(Object other) { 273 if (other == null) return false; 274 if (!(other instanceof PerAppInfo)) return false; 275 PerAppInfo otherPerAppInfo = (PerAppInfo) other; 276 return uid == otherPerAppInfo.uid 277 && TextUtils.equals(packageName, otherPerAppInfo.packageName) 278 && Objects.equals(extNetworkSuggestions, otherPerAppInfo.extNetworkSuggestions) 279 && hasUserApproved == otherPerAppInfo.hasUserApproved; 280 } 281 282 // This is only needed for comparison in unit tests. 283 @Override hashCode()284 public int hashCode() { 285 return Objects.hash(uid, packageName, extNetworkSuggestions, hasUserApproved); 286 } 287 288 @Override toString()289 public String toString() { 290 return new StringBuilder("PerAppInfo[ ") 291 .append("uid=").append(uid) 292 .append(", packageName=").append(packageName) 293 .append(", hasUserApproved=").append(hasUserApproved) 294 .append(", suggestions=").append(extNetworkSuggestions) 295 .append(" ]") 296 .toString(); 297 } 298 } 299 300 /** 301 * Internal container class which holds a network suggestion and a pointer to the 302 * {@link PerAppInfo} entry from {@link #mActiveNetworkSuggestionsPerApp} corresponding to the 303 * app that made the suggestion. 304 */ 305 public static class ExtendedWifiNetworkSuggestion { 306 public final WifiNetworkSuggestion wns; 307 // Store the pointer to the corresponding app's meta data. 308 public final PerAppInfo perAppInfo; 309 public boolean isAutojoinEnabled; 310 public String anonymousIdentity = null; 311 public String connectChoice = null; 312 public int connectChoiceRssi = 0; 313 ExtendedWifiNetworkSuggestion(@onNull WifiNetworkSuggestion wns, @NonNull PerAppInfo perAppInfo, boolean isAutoJoinEnabled)314 public ExtendedWifiNetworkSuggestion(@NonNull WifiNetworkSuggestion wns, 315 @NonNull PerAppInfo perAppInfo, 316 boolean isAutoJoinEnabled) { 317 this.wns = wns; 318 this.perAppInfo = perAppInfo; 319 this.isAutojoinEnabled = isAutoJoinEnabled; 320 this.wns.wifiConfiguration.fromWifiNetworkSuggestion = true; 321 this.wns.wifiConfiguration.ephemeral = true; 322 this.wns.wifiConfiguration.creatorName = perAppInfo.packageName; 323 this.wns.wifiConfiguration.creatorUid = perAppInfo.uid; 324 if (perAppInfo.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) { 325 return; 326 } 327 // If App is carrier privileged, set carrier Id to the profile. 328 this.wns.wifiConfiguration.carrierId = perAppInfo.carrierId; 329 if (this.wns.passpointConfiguration != null) { 330 this.wns.passpointConfiguration.setCarrierId(perAppInfo.carrierId); 331 } 332 } 333 334 @Override hashCode()335 public int hashCode() { 336 return Objects.hash(wns, perAppInfo.uid, perAppInfo.packageName); 337 } 338 339 @Override equals(Object obj)340 public boolean equals(Object obj) { 341 if (this == obj) { 342 return true; 343 } 344 if (!(obj instanceof ExtendedWifiNetworkSuggestion)) { 345 return false; 346 } 347 ExtendedWifiNetworkSuggestion other = (ExtendedWifiNetworkSuggestion) obj; 348 return wns.equals(other.wns) 349 && perAppInfo.uid == other.perAppInfo.uid 350 && TextUtils.equals(perAppInfo.packageName, other.perAppInfo.packageName); 351 } 352 353 @Override toString()354 public String toString() { 355 return new StringBuilder(wns.toString()) 356 .append(", isAutoJoinEnabled=").append(isAutojoinEnabled) 357 .toString(); 358 } 359 360 /** 361 * Convert from {@link WifiNetworkSuggestion} to a new instance of 362 * {@link ExtendedWifiNetworkSuggestion}. 363 */ fromWns(@onNull WifiNetworkSuggestion wns, @NonNull PerAppInfo perAppInfo, boolean isAutoJoinEnabled)364 public static ExtendedWifiNetworkSuggestion fromWns(@NonNull WifiNetworkSuggestion wns, 365 @NonNull PerAppInfo perAppInfo, boolean isAutoJoinEnabled) { 366 return new ExtendedWifiNetworkSuggestion(wns, perAppInfo, isAutoJoinEnabled); 367 } 368 369 /** 370 * Create a {@link WifiConfiguration} from suggestion for framework internal use. 371 */ createInternalWifiConfiguration( @ullable WifiCarrierInfoManager carrierInfoManager)372 public WifiConfiguration createInternalWifiConfiguration( 373 @Nullable WifiCarrierInfoManager carrierInfoManager) { 374 WifiConfiguration config = new WifiConfiguration(wns.getWifiConfiguration()); 375 config.shared = false; 376 config.allowAutojoin = isAutojoinEnabled; 377 if (config.enterpriseConfig 378 != null && config.enterpriseConfig.isAuthenticationSimBased()) { 379 config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity); 380 } 381 config.getNetworkSelectionStatus().setConnectChoice(connectChoice); 382 config.getNetworkSelectionStatus().setConnectChoiceRssi(connectChoiceRssi); 383 if (carrierInfoManager != null) { 384 config.subscriptionId = carrierInfoManager.getBestMatchSubscriptionId(config); 385 // shouldDisableMacRandomization checks if the SSID matches with a SSID configured 386 // in CarrierConfigManger for the provided subscriptionId. 387 if (carrierInfoManager.shouldDisableMacRandomization(config.SSID, 388 config.carrierId, config.subscriptionId)) { 389 Log.i(TAG, "Disabling MAC randomization on " + config.SSID 390 + " due to CarrierConfig override"); 391 config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE; 392 } 393 } 394 return config; 395 } 396 } 397 398 /** 399 * Map of package name of an app to the set of active network suggestions provided by the app. 400 */ 401 private final Map<String, PerAppInfo> mActiveNetworkSuggestionsPerApp = new HashMap<>(); 402 /** 403 * Map of package name of an app to the app ops changed listener for the app. 404 */ 405 private final Map<String, AppOpsChangedListener> mAppOpsChangedListenerPerApp = new HashMap<>(); 406 /** 407 * Map maintained to help lookup all the network suggestions (with no bssid) that match a 408 * provided scan result. 409 * Note: 410 * <li>There could be multiple suggestions (provided by different apps) that match a single 411 * scan result.</li> 412 * <li>Adding/Removing to this set for scan result lookup is expensive. But, we expect scan 413 * result lookup to happen much more often than apps modifying network suggestions.</li> 414 */ 415 private final Map<ScanResultMatchInfo, Set<ExtendedWifiNetworkSuggestion>> 416 mActiveScanResultMatchInfoWithNoBssid = new HashMap<>(); 417 /** 418 * Map maintained to help lookup all the network suggestions (with bssid) that match a provided 419 * scan result. 420 * Note: 421 * <li>There could be multiple suggestions (provided by different apps) that match a single 422 * scan result.</li> 423 * <li>Adding/Removing to this set for scan result lookup is expensive. But, we expect scan 424 * result lookup to happen much more often than apps modifying network suggestions.</li> 425 */ 426 private final Map<Pair<ScanResultMatchInfo, MacAddress>, Set<ExtendedWifiNetworkSuggestion>> 427 mActiveScanResultMatchInfoWithBssid = new HashMap<>(); 428 429 private final Map<String, Set<ExtendedWifiNetworkSuggestion>> 430 mPasspointInfo = new HashMap<>(); 431 432 private final HashMap<String, RemoteCallbackList<ISuggestionConnectionStatusListener>> 433 mSuggestionStatusListenerPerApp = new HashMap<>(); 434 435 private final HashMap<String, RemoteCallbackList<ISuggestionUserApprovalStatusListener>> 436 mSuggestionUserApprovalStatusListenerPerApp = new HashMap<>(); 437 438 /** 439 * Store the suggestion update listeners. 440 */ 441 private final List<OnSuggestionUpdateListener> mListeners = new ArrayList<>(); 442 443 /** 444 * Intent filter for processing notification actions. 445 */ 446 private final IntentFilter mIntentFilter; 447 448 /** 449 * Verbose logging flag. 450 */ 451 private boolean mVerboseLoggingEnabled = false; 452 /** 453 * Indicates that we have new data to serialize. 454 */ 455 private boolean mHasNewDataToSerialize = false; 456 /** 457 * The {@link Clock#getElapsedSinceBootMillis()} must be at least this value for us 458 * to update/show the notification. 459 */ 460 private long mNotificationUpdateTime; 461 462 private boolean mIsLastUserApprovalUiDialog = false; 463 464 private boolean mUserDataLoaded = false; 465 466 /** 467 * Keep a set of packageNames which is treated as carrier provider. 468 */ 469 private final Set<String> mCrossCarrierProvidersSet = new ArraySet<>(); 470 471 /** 472 * Listener for app-ops changes for active suggestor apps. 473 */ 474 private final class AppOpsChangedListener implements AppOpsManager.OnOpChangedListener { 475 private final String mPackageName; 476 private final int mUid; 477 AppOpsChangedListener(@onNull String packageName, int uid)478 AppOpsChangedListener(@NonNull String packageName, int uid) { 479 mPackageName = packageName; 480 mUid = uid; 481 } 482 483 @Override onOpChanged(String op, String packageName)484 public void onOpChanged(String op, String packageName) { 485 mHandler.post(() -> { 486 if (!mPackageName.equals(packageName)) return; 487 if (!OPSTR_CHANGE_WIFI_STATE.equals(op)) return; 488 489 // Ensure the uid to package mapping is still correct. 490 try { 491 mAppOps.checkPackage(mUid, mPackageName); 492 } catch (SecurityException e) { 493 Log.wtf(TAG, "Invalid uid/package" + packageName); 494 return; 495 } 496 497 if (mAppOps.unsafeCheckOpNoThrow(OPSTR_CHANGE_WIFI_STATE, mUid, mPackageName) 498 == AppOpsManager.MODE_IGNORED) { 499 Log.i(TAG, "User disallowed change wifi state for " + packageName); 500 // User disabled the app, remove app from database. We want the notification 501 // again if the user enabled the app-op back. 502 removeApp(mPackageName); 503 mWifiMetrics.incrementNetworkSuggestionUserRevokePermission(); 504 } 505 }); 506 } 507 }; 508 509 /** 510 * Module to interact with the wifi config store. 511 */ 512 private class NetworkSuggestionDataSource implements NetworkSuggestionStoreData.DataSource { 513 @Override toSerialize()514 public Map<String, PerAppInfo> toSerialize() { 515 for (Map.Entry<String, PerAppInfo> entry : mActiveNetworkSuggestionsPerApp.entrySet()) { 516 for (ExtendedWifiNetworkSuggestion ewns : entry.getValue().extNetworkSuggestions 517 .values()) { 518 if (ewns.wns.passpointConfiguration != null) { 519 continue; 520 } 521 ewns.wns.wifiConfiguration.isMostRecentlyConnected = mLruConnectionTracker 522 .isMostRecentlyConnected(ewns.createInternalWifiConfiguration( 523 mWifiCarrierInfoManager)); 524 } 525 } 526 // Clear the flag after writing to disk. 527 // TODO(b/115504887): Don't reset the flag on write failure. 528 mHasNewDataToSerialize = false; 529 return mActiveNetworkSuggestionsPerApp; 530 } 531 532 @Override fromDeserialized(Map<String, PerAppInfo> networkSuggestionsMap)533 public void fromDeserialized(Map<String, PerAppInfo> networkSuggestionsMap) { 534 mActiveNetworkSuggestionsPerApp.clear(); 535 mActiveNetworkSuggestionsPerApp.putAll(networkSuggestionsMap); 536 // Build the scan cache. 537 for (Map.Entry<String, PerAppInfo> entry : networkSuggestionsMap.entrySet()) { 538 String packageName = entry.getKey(); 539 Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = 540 entry.getValue().extNetworkSuggestions.values(); 541 if (!extNetworkSuggestions.isEmpty()) { 542 // Start tracking app-op changes from the app if they have active suggestions. 543 startTrackingAppOpsChange(packageName, 544 extNetworkSuggestions.iterator().next().perAppInfo.uid); 545 } 546 for (ExtendedWifiNetworkSuggestion ewns : extNetworkSuggestions) { 547 if (ewns.wns.passpointConfiguration != null) { 548 addToPasspointInfoMap(ewns); 549 } else { 550 if (ewns.wns.wifiConfiguration.isMostRecentlyConnected) { 551 mLruConnectionTracker 552 .addNetwork(ewns.createInternalWifiConfiguration( 553 mWifiCarrierInfoManager)); 554 } 555 addToScanResultMatchInfoMap(ewns); 556 } 557 } 558 } 559 mUserDataLoaded = true; 560 } 561 562 @Override reset()563 public void reset() { 564 mUserDataLoaded = false; 565 mActiveNetworkSuggestionsPerApp.clear(); 566 mActiveScanResultMatchInfoWithBssid.clear(); 567 mActiveScanResultMatchInfoWithNoBssid.clear(); 568 mPasspointInfo.clear(); 569 } 570 571 @Override hasNewDataToSerialize()572 public boolean hasNewDataToSerialize() { 573 return mHasNewDataToSerialize; 574 } 575 } 576 handleUserAllowAction(int uid, String packageName)577 private void handleUserAllowAction(int uid, String packageName) { 578 Log.i(TAG, "User clicked to allow app"); 579 // Set the user approved flag. 580 setHasUserApprovedForApp(true, uid, packageName); 581 mNotificationUpdateTime = 0; 582 mWifiMetrics.addUserApprovalSuggestionAppUiReaction( 583 ACTION_USER_ALLOWED_APP, 584 mIsLastUserApprovalUiDialog); 585 } 586 handleUserDisallowAction(int uid, String packageName)587 private void handleUserDisallowAction(int uid, String packageName) { 588 Log.i(TAG, "User clicked to disallow app"); 589 // Take away CHANGE_WIFI_STATE app-ops from the app. 590 mAppOps.setMode(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, uid, packageName, 591 MODE_IGNORED); 592 // Set the user approved flag. 593 setHasUserApprovedForApp(false, uid, packageName); 594 mNotificationUpdateTime = 0; 595 mWifiMetrics.addUserApprovalSuggestionAppUiReaction( 596 ACTION_USER_DISALLOWED_APP, 597 mIsLastUserApprovalUiDialog); 598 } 599 handleUserDismissAction()600 private void handleUserDismissAction() { 601 Log.i(TAG, "User dismissed the notification"); 602 mNotificationUpdateTime = 0; 603 mWifiMetrics.addUserApprovalSuggestionAppUiReaction( 604 ACTION_USER_DISMISS, 605 mIsLastUserApprovalUiDialog); 606 } 607 608 private final BroadcastReceiver mBroadcastReceiver = 609 new BroadcastReceiver() { 610 @Override 611 public void onReceive(Context context, Intent intent) { 612 String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME); 613 int uid = intent.getIntExtra(EXTRA_UID, -1); 614 if (packageName == null || uid == -1) { 615 Log.e(TAG, "No package name or uid found in intent"); 616 return; 617 } 618 switch (intent.getAction()) { 619 case NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION: 620 handleUserAllowAction(uid, packageName); 621 break; 622 case NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION: 623 handleUserDisallowAction(uid, packageName); 624 break; 625 case NOTIFICATION_USER_DISMISSED_INTENT_ACTION: 626 handleUserDismissAction(); 627 return; // no need to cancel a dismissed notification, return. 628 default: 629 Log.e(TAG, "Unknown action " + intent.getAction()); 630 return; 631 } 632 // Clear notification once the user interacts with it. 633 mNotificationManager.cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE); 634 } 635 }; 636 637 /** 638 * Interface for other modules to listen to the suggestion updated events. 639 */ 640 public interface OnSuggestionUpdateListener { 641 /** 642 * Invoked on suggestion being added or updated. 643 */ onSuggestionsAddedOrUpdated(@onNull List<WifiNetworkSuggestion> addedSuggestions)644 void onSuggestionsAddedOrUpdated(@NonNull List<WifiNetworkSuggestion> addedSuggestions); 645 /** 646 * Invoked on suggestion being removed. 647 */ onSuggestionsRemoved(@onNull List<WifiNetworkSuggestion> removedSuggestions)648 void onSuggestionsRemoved(@NonNull List<WifiNetworkSuggestion> removedSuggestions); 649 } 650 651 private final class UserApproveCarrierListener implements 652 WifiCarrierInfoManager.OnUserApproveCarrierListener { 653 654 @Override onUserAllowed(int carrierId)655 public void onUserAllowed(int carrierId) { 656 restoreInitialAutojoinForCarrierId(carrierId); 657 } 658 } 659 WifiNetworkSuggestionsManager(WifiContext context, Handler handler, WifiInjector wifiInjector, WifiPermissionsUtil wifiPermissionsUtil, WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore, WifiMetrics wifiMetrics, WifiCarrierInfoManager wifiCarrierInfoManager, WifiKeyStore keyStore, LruConnectionTracker lruConnectionTracker, Clock clock)660 public WifiNetworkSuggestionsManager(WifiContext context, Handler handler, 661 WifiInjector wifiInjector, WifiPermissionsUtil wifiPermissionsUtil, 662 WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore, 663 WifiMetrics wifiMetrics, WifiCarrierInfoManager wifiCarrierInfoManager, 664 WifiKeyStore keyStore, LruConnectionTracker lruConnectionTracker, 665 Clock clock) { 666 mContext = context; 667 mResources = context.getResources(); 668 mHandler = handler; 669 mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 670 mActivityManager = context.getSystemService(ActivityManager.class); 671 mWifiInjector = wifiInjector; 672 mFrameworkFacade = mWifiInjector.getFrameworkFacade(); 673 mWifiPermissionsUtil = wifiPermissionsUtil; 674 mWifiConfigManager = wifiConfigManager; 675 mWifiMetrics = wifiMetrics; 676 mWifiCarrierInfoManager = wifiCarrierInfoManager; 677 mWifiKeyStore = keyStore; 678 mNotificationManager = mWifiInjector.getWifiNotificationManager(); 679 mClock = clock; 680 681 // register the data store for serializing/deserializing data. 682 wifiConfigStore.registerStoreData( 683 wifiInjector.makeNetworkSuggestionStoreData(new NetworkSuggestionDataSource())); 684 685 mWifiCarrierInfoManager.addImsiExemptionUserApprovalListener( 686 new UserApproveCarrierListener()); 687 688 // Register broadcast receiver for UI interactions. 689 mIntentFilter = new IntentFilter(); 690 mIntentFilter.addAction(NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION); 691 mIntentFilter.addAction(NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION); 692 mIntentFilter.addAction(NOTIFICATION_USER_DISMISSED_INTENT_ACTION); 693 694 mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, handler); 695 mLruConnectionTracker = lruConnectionTracker; 696 mHandler.postAtFrontOfQueue(() -> mWifiConfigManager.addOnNetworkUpdateListener( 697 new WifiNetworkSuggestionsManager.OnNetworkUpdateListener())); 698 } 699 700 /** 701 * Enable verbose logging. 702 */ enableVerboseLogging(boolean verboseEnabled)703 public void enableVerboseLogging(boolean verboseEnabled) { 704 mVerboseLoggingEnabled = verboseEnabled; 705 } 706 saveToStore()707 private void saveToStore() { 708 // Set the flag to let WifiConfigStore that we have new data to write. 709 mHasNewDataToSerialize = true; 710 if (!mWifiConfigManager.saveToStore(true)) { 711 Log.w(TAG, "Failed to save to store"); 712 } 713 } 714 addToScanResultMatchInfoMap( @onNull ExtendedWifiNetworkSuggestion extNetworkSuggestion)715 private void addToScanResultMatchInfoMap( 716 @NonNull ExtendedWifiNetworkSuggestion extNetworkSuggestion) { 717 ScanResultMatchInfo scanResultMatchInfo = 718 ScanResultMatchInfo.fromWifiConfiguration( 719 extNetworkSuggestion.wns.wifiConfiguration); 720 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsForScanResultMatchInfo; 721 if (!TextUtils.isEmpty(extNetworkSuggestion.wns.wifiConfiguration.BSSID)) { 722 Pair<ScanResultMatchInfo, MacAddress> lookupPair = 723 Pair.create(scanResultMatchInfo, 724 MacAddress.fromString( 725 extNetworkSuggestion.wns.wifiConfiguration.BSSID)); 726 extNetworkSuggestionsForScanResultMatchInfo = 727 mActiveScanResultMatchInfoWithBssid.get(lookupPair); 728 if (extNetworkSuggestionsForScanResultMatchInfo == null) { 729 extNetworkSuggestionsForScanResultMatchInfo = new HashSet<>(); 730 mActiveScanResultMatchInfoWithBssid.put( 731 lookupPair, extNetworkSuggestionsForScanResultMatchInfo); 732 } 733 } else { 734 extNetworkSuggestionsForScanResultMatchInfo = 735 mActiveScanResultMatchInfoWithNoBssid.get(scanResultMatchInfo); 736 if (extNetworkSuggestionsForScanResultMatchInfo == null) { 737 extNetworkSuggestionsForScanResultMatchInfo = new HashSet<>(); 738 mActiveScanResultMatchInfoWithNoBssid.put( 739 scanResultMatchInfo, extNetworkSuggestionsForScanResultMatchInfo); 740 } 741 } 742 extNetworkSuggestionsForScanResultMatchInfo.remove(extNetworkSuggestion); 743 extNetworkSuggestionsForScanResultMatchInfo.add(extNetworkSuggestion); 744 } 745 removeFromScanResultMatchInfoMapAndRemoveRelatedScoreCard( @onNull ExtendedWifiNetworkSuggestion extNetworkSuggestion, boolean removeScoreCard)746 private void removeFromScanResultMatchInfoMapAndRemoveRelatedScoreCard( 747 @NonNull ExtendedWifiNetworkSuggestion extNetworkSuggestion, boolean removeScoreCard) { 748 ScanResultMatchInfo scanResultMatchInfo = 749 ScanResultMatchInfo.fromWifiConfiguration( 750 extNetworkSuggestion.wns.wifiConfiguration); 751 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsForScanResultMatchInfo; 752 if (!TextUtils.isEmpty(extNetworkSuggestion.wns.wifiConfiguration.BSSID)) { 753 Pair<ScanResultMatchInfo, MacAddress> lookupPair = 754 Pair.create(scanResultMatchInfo, 755 MacAddress.fromString( 756 extNetworkSuggestion.wns.wifiConfiguration.BSSID)); 757 extNetworkSuggestionsForScanResultMatchInfo = 758 mActiveScanResultMatchInfoWithBssid.get(lookupPair); 759 // This should never happen because we should have done necessary error checks in 760 // the parent method. 761 if (extNetworkSuggestionsForScanResultMatchInfo == null) { 762 Log.wtf(TAG, "No scan result match info found."); 763 return; 764 } 765 extNetworkSuggestionsForScanResultMatchInfo.remove(extNetworkSuggestion); 766 // Remove the set from map if empty. 767 if (extNetworkSuggestionsForScanResultMatchInfo.isEmpty()) { 768 mActiveScanResultMatchInfoWithBssid.remove(lookupPair); 769 if (!mActiveScanResultMatchInfoWithNoBssid.containsKey(scanResultMatchInfo)) { 770 if (removeScoreCard) { 771 removeNetworkFromScoreCard(extNetworkSuggestion.wns.wifiConfiguration); 772 } 773 mLruConnectionTracker.removeNetwork( 774 extNetworkSuggestion.wns.wifiConfiguration); 775 } 776 } 777 } else { 778 extNetworkSuggestionsForScanResultMatchInfo = 779 mActiveScanResultMatchInfoWithNoBssid.get(scanResultMatchInfo); 780 // This should never happen because we should have done necessary error checks in 781 // the parent method. 782 if (extNetworkSuggestionsForScanResultMatchInfo == null) { 783 Log.wtf(TAG, "No scan result match info found."); 784 return; 785 } 786 extNetworkSuggestionsForScanResultMatchInfo.remove(extNetworkSuggestion); 787 // Remove the set from map if empty. 788 if (extNetworkSuggestionsForScanResultMatchInfo.isEmpty()) { 789 mActiveScanResultMatchInfoWithNoBssid.remove(scanResultMatchInfo); 790 if (removeScoreCard) { 791 removeNetworkFromScoreCard(extNetworkSuggestion.wns.wifiConfiguration); 792 } 793 mLruConnectionTracker.removeNetwork( 794 extNetworkSuggestion.wns.wifiConfiguration); 795 } 796 } 797 } 798 removeNetworkFromScoreCard(WifiConfiguration wifiConfiguration)799 private void removeNetworkFromScoreCard(WifiConfiguration wifiConfiguration) { 800 WifiConfiguration existing = 801 mWifiConfigManager.getConfiguredNetwork(wifiConfiguration.getProfileKey()); 802 // If there is a saved network, do not remove from the score card. 803 if (existing != null && !existing.fromWifiNetworkSuggestion) { 804 return; 805 } 806 mWifiInjector.getWifiScoreCard().removeNetwork(wifiConfiguration.SSID); 807 } 808 addToPasspointInfoMap(ExtendedWifiNetworkSuggestion ewns)809 private void addToPasspointInfoMap(ExtendedWifiNetworkSuggestion ewns) { 810 Set<ExtendedWifiNetworkSuggestion> extendedWifiNetworkSuggestions = 811 mPasspointInfo.get(ewns.wns.wifiConfiguration.FQDN); 812 if (extendedWifiNetworkSuggestions == null) { 813 extendedWifiNetworkSuggestions = new HashSet<>(); 814 } 815 extendedWifiNetworkSuggestions.remove(ewns); 816 extendedWifiNetworkSuggestions.add(ewns); 817 mPasspointInfo.put(ewns.wns.wifiConfiguration.FQDN, extendedWifiNetworkSuggestions); 818 } 819 removeFromPassPointInfoMap(ExtendedWifiNetworkSuggestion ewns)820 private void removeFromPassPointInfoMap(ExtendedWifiNetworkSuggestion ewns) { 821 Set<ExtendedWifiNetworkSuggestion> extendedWifiNetworkSuggestions = 822 mPasspointInfo.get(ewns.wns.wifiConfiguration.FQDN); 823 if (extendedWifiNetworkSuggestions == null 824 || !extendedWifiNetworkSuggestions.contains(ewns)) { 825 Log.wtf(TAG, "No Passpoint info found."); 826 return; 827 } 828 extendedWifiNetworkSuggestions.remove(ewns); 829 if (extendedWifiNetworkSuggestions.isEmpty()) { 830 mPasspointInfo.remove(ewns.wns.wifiConfiguration.FQDN); 831 } 832 } 833 startTrackingAppOpsChange(@onNull String packageName, int uid)834 private void startTrackingAppOpsChange(@NonNull String packageName, int uid) { 835 AppOpsChangedListener appOpsChangedListener = 836 new AppOpsChangedListener(packageName, uid); 837 mAppOps.startWatchingMode(OPSTR_CHANGE_WIFI_STATE, packageName, appOpsChangedListener); 838 mAppOpsChangedListenerPerApp.put(packageName, appOpsChangedListener); 839 } 840 841 /** 842 * Helper method to convert the incoming collection of public {@link WifiNetworkSuggestion} 843 * objects to a set of corresponding internal wrapper 844 * {@link ExtendedWifiNetworkSuggestion} objects. 845 */ convertToExtendedWnsSet( final Collection<WifiNetworkSuggestion> networkSuggestions, final PerAppInfo perAppInfo)846 private Set<ExtendedWifiNetworkSuggestion> convertToExtendedWnsSet( 847 final Collection<WifiNetworkSuggestion> networkSuggestions, 848 final PerAppInfo perAppInfo) { 849 return networkSuggestions 850 .stream() 851 .map(n -> ExtendedWifiNetworkSuggestion.fromWns(n, perAppInfo, 852 n.isInitialAutoJoinEnabled)) 853 .collect(Collectors.toSet()); 854 } 855 856 /** 857 * Helper method to convert the incoming collection of internal wrapper 858 * {@link ExtendedWifiNetworkSuggestion} objects to a set of corresponding public 859 * {@link WifiNetworkSuggestion} objects. 860 */ convertToWnsSet( final Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions)861 private Set<WifiNetworkSuggestion> convertToWnsSet( 862 final Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions) { 863 return extNetworkSuggestions 864 .stream() 865 .map(n -> n.wns) 866 .collect(Collectors.toSet()); 867 } 868 updateWifiConfigInWcmIfPresent( WifiConfiguration newConfig, int uid, String packageName)869 private void updateWifiConfigInWcmIfPresent( 870 WifiConfiguration newConfig, int uid, String packageName) { 871 WifiConfiguration configInWcm = 872 mWifiConfigManager.getConfiguredNetwork(newConfig.getProfileKey()); 873 if (configInWcm == null) return; 874 // !suggestion 875 if (!configInWcm.fromWifiNetworkSuggestion) return; 876 // is suggestion from same app. 877 if (configInWcm.creatorUid != uid 878 || !TextUtils.equals(configInWcm.creatorName, packageName)) { 879 return; 880 } 881 NetworkUpdateResult result = mWifiConfigManager.addOrUpdateNetwork( 882 newConfig, uid, packageName, false); 883 if (!result.isSuccess()) { 884 Log.e(TAG, "Failed to update config in WifiConfigManager"); 885 return; 886 } 887 if (mVerboseLoggingEnabled) { 888 Log.v(TAG, "Updated config in WifiConfigManager"); 889 } 890 } 891 892 /** 893 * Add the provided list of network suggestions from the corresponding app's active list. 894 */ add( List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName, @Nullable String featureId)895 public @WifiManager.NetworkSuggestionsStatusCode int add( 896 List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName, 897 @Nullable String featureId) { 898 if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(uid)) { 899 Log.e(TAG, "UID " + uid + " not visible to the current user"); 900 return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL; 901 } 902 if (!mUserDataLoaded) { 903 Log.e(TAG, "Add Network suggestion before boot complete is not allowed."); 904 return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL; 905 } 906 if (networkSuggestions == null || networkSuggestions.isEmpty()) { 907 Log.w(TAG, "Empty list of network suggestions for " + packageName + ". Ignoring"); 908 return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; 909 } 910 if (mVerboseLoggingEnabled) { 911 Log.v(TAG, "Adding " + networkSuggestions.size() + " networks from " + packageName); 912 } 913 if (!validateNetworkSuggestions(networkSuggestions, packageName, uid)) { 914 Log.e(TAG, "Invalid suggestion add from app: " + packageName); 915 return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID; 916 } 917 int carrierId = mWifiCarrierInfoManager 918 .getCarrierIdForPackageWithCarrierPrivileges(packageName); 919 if (!validateCarrierNetworkSuggestions(networkSuggestions, uid, packageName, carrierId)) { 920 Log.e(TAG, "bad wifi suggestion from app: " + packageName); 921 return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED; 922 } 923 for (WifiNetworkSuggestion wns : networkSuggestions) { 924 wns.wifiConfiguration.convertLegacyFieldsToSecurityParamsIfNeeded(); 925 if (!WifiConfigurationUtil.addUpgradableSecurityTypeIfNecessary( 926 wns.wifiConfiguration)) { 927 Log.e(TAG, "Invalid suggestion add from app: " + packageName); 928 return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID; 929 } 930 } 931 932 PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName); 933 if (perAppInfo == null) { 934 perAppInfo = new PerAppInfo(uid, packageName, featureId); 935 mActiveNetworkSuggestionsPerApp.put(packageName, perAppInfo); 936 if (mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(uid)) { 937 Log.i(TAG, "Setting the carrier provisioning app approved"); 938 perAppInfo.hasUserApproved = true; 939 mWifiMetrics.incrementNetworkSuggestionApiUsageNumOfAppInType( 940 APP_TYPE_NETWORK_PROVISIONING); 941 } else if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid) 942 || isAppWorkingAsCrossCarrierProvider(packageName)) { 943 // Bypass added for tests & shell commands. 944 Log.i(TAG, "Setting the test app approved"); 945 perAppInfo.hasUserApproved = true; 946 } else if (carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) { 947 Log.i(TAG, "Setting the carrier privileged app approved"); 948 perAppInfo.setCarrierId(carrierId); 949 mWifiMetrics.incrementNetworkSuggestionApiUsageNumOfAppInType( 950 APP_TYPE_CARRIER_PRIVILEGED); 951 } else { 952 if (isSuggestionFromForegroundApp(packageName)) { 953 sendUserApprovalDialog(packageName, uid); 954 } else { 955 sendUserApprovalNotificationIfNotApproved(packageName, uid); 956 } 957 mWifiMetrics.incrementNetworkSuggestionApiUsageNumOfAppInType( 958 APP_TYPE_NON_PRIVILEGED); 959 } 960 onSuggestionUserApprovalStatusChanged(uid, packageName); 961 } 962 // If PerAppInfo is upgrade from pre-R, uid may not be set. 963 perAppInfo.setUid(uid); 964 // If App became carrier privileged, set the carrier Id. 965 perAppInfo.setCarrierId(carrierId); 966 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = 967 convertToExtendedWnsSet(networkSuggestions, perAppInfo); 968 boolean isLowRamDevice = mActivityManager.isLowRamDevice(); 969 int networkSuggestionsMaxPerApp = 970 WifiManager.getMaxNumberOfNetworkSuggestionsPerApp(isLowRamDevice); 971 if (perAppInfo.extNetworkSuggestions.size() + extNetworkSuggestions.size() 972 > networkSuggestionsMaxPerApp) { 973 Set<Integer> keySet = extNetworkSuggestions 974 .stream() 975 .map(ExtendedWifiNetworkSuggestion::hashCode) 976 .collect(Collectors.toSet()); 977 Set<Integer> savedKeySet = new HashSet<>(perAppInfo.extNetworkSuggestions.keySet()); 978 savedKeySet.addAll(keySet); 979 if (savedKeySet.size() > networkSuggestionsMaxPerApp) { 980 Log.e(TAG, "Failed to add network suggestions for " + packageName 981 + ". Exceeds max per app, current list size: " 982 + perAppInfo.extNetworkSuggestions.size() 983 + ", new list size: " 984 + extNetworkSuggestions.size()); 985 return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP; 986 } 987 } 988 if (perAppInfo.extNetworkSuggestions.isEmpty()) { 989 // Start tracking app-op changes from the app if they have active suggestions. 990 startTrackingAppOpsChange(packageName, uid); 991 } 992 993 for (ExtendedWifiNetworkSuggestion ewns: extNetworkSuggestions) { 994 ExtendedWifiNetworkSuggestion oldEwns = perAppInfo.extNetworkSuggestions 995 .get(ewns.hashCode()); 996 // Keep the user connect choice and AnonymousIdentity 997 if (oldEwns != null) { 998 ewns.connectChoice = oldEwns.connectChoice; 999 ewns.connectChoiceRssi = oldEwns.connectChoiceRssi; 1000 ewns.anonymousIdentity = oldEwns.anonymousIdentity; 1001 // If user change the auto-join, keep the user choice. 1002 if (oldEwns.isAutojoinEnabled != oldEwns.wns.isInitialAutoJoinEnabled) { 1003 ewns.isAutojoinEnabled = oldEwns.isAutojoinEnabled; 1004 } 1005 } 1006 // If network has no IMSI protection and user didn't approve exemption, make it initial 1007 // auto join disabled 1008 if (isSimBasedPhase1Suggestion(ewns)) { 1009 int subId = mWifiCarrierInfoManager 1010 .getMatchingSubId(getCarrierIdFromSuggestion(ewns)); 1011 if (!(mWifiCarrierInfoManager.requiresImsiEncryption(subId) 1012 || mWifiCarrierInfoManager.hasUserApprovedImsiPrivacyExemptionForCarrier( 1013 getCarrierIdFromSuggestion(ewns)))) { 1014 ewns.isAutojoinEnabled = false; 1015 } 1016 } 1017 mWifiMetrics.addNetworkSuggestionPriorityGroup(ewns.wns.priorityGroup); 1018 if (ewns.wns.passpointConfiguration == null) { 1019 if (ewns.wns.wifiConfiguration.isEnterprise()) { 1020 if (!mWifiKeyStore.updateNetworkKeys(ewns.wns.wifiConfiguration, null)) { 1021 Log.e(TAG, "Enterprise network install failure for SSID: " 1022 + ewns.wns.wifiConfiguration.SSID); 1023 continue; 1024 } 1025 } 1026 // If we have a config in WifiConfigManager for this suggestion, update 1027 // WifiConfigManager with the latest WifiConfig. 1028 // Note: Similar logic is present in PasspointManager for passpoint networks. 1029 updateWifiConfigInWcmIfPresent(ewns.createInternalWifiConfiguration( 1030 mWifiCarrierInfoManager), uid, packageName); 1031 addToScanResultMatchInfoMap(ewns); 1032 } else { 1033 ewns.wns.passpointConfiguration.setAutojoinEnabled(ewns.isAutojoinEnabled); 1034 // Install Passpoint config, if failure, ignore that suggestion 1035 if (!mWifiInjector.getPasspointManager().addOrUpdateProvider( 1036 ewns.wns.passpointConfiguration, uid, 1037 packageName, true, !ewns.wns.isUntrusted())) { 1038 Log.e(TAG, "Passpoint profile install failure for FQDN: " 1039 + ewns.wns.wifiConfiguration.FQDN); 1040 continue; 1041 } 1042 addToPasspointInfoMap(ewns); 1043 } 1044 perAppInfo.extNetworkSuggestions.remove(ewns.hashCode()); 1045 perAppInfo.extNetworkSuggestions.put(ewns.hashCode(), ewns); 1046 } 1047 for (OnSuggestionUpdateListener listener : mListeners) { 1048 listener.onSuggestionsAddedOrUpdated(networkSuggestions); 1049 } 1050 // Update the max size for this app. 1051 perAppInfo.maxSize = Math.max(perAppInfo.extNetworkSuggestions.size(), perAppInfo.maxSize); 1052 try { 1053 saveToStore(); 1054 } catch (OutOfMemoryError e) { 1055 Optional<PerAppInfo> appInfo = mActiveNetworkSuggestionsPerApp.values() 1056 .stream() 1057 .max(Comparator.comparingInt(a -> a.extNetworkSuggestions.size())); 1058 if (appInfo.isPresent()) { 1059 EventLog.writeEvent(0x534e4554, "245299920", appInfo.get().uid, 1060 "Trying to add large number of suggestion, num=" 1061 + appInfo.get().extNetworkSuggestions.size()); 1062 } else { 1063 Log.e(TAG, "serialize out of memory but no app has suggestion!"); 1064 } 1065 // Remove the most recently added suggestions, which should cause the failure. 1066 remove(networkSuggestions, uid, packageName, ACTION_REMOVE_SUGGESTION_DISCONNECT); 1067 return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL; 1068 } 1069 mWifiMetrics.incrementNetworkSuggestionApiNumModification(); 1070 mWifiMetrics.noteNetworkSuggestionApiListSizeHistogram(getAllMaxSizes()); 1071 return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; 1072 } 1073 getCarrierIdFromSuggestion(ExtendedWifiNetworkSuggestion ewns)1074 private int getCarrierIdFromSuggestion(ExtendedWifiNetworkSuggestion ewns) { 1075 if (ewns.wns.passpointConfiguration == null) { 1076 return ewns.wns.wifiConfiguration.carrierId; 1077 } 1078 return ewns.wns.passpointConfiguration.getCarrierId(); 1079 } 1080 isSimBasedPhase1Suggestion(ExtendedWifiNetworkSuggestion ewns)1081 private boolean isSimBasedPhase1Suggestion(ExtendedWifiNetworkSuggestion ewns) { 1082 if (ewns.wns.passpointConfiguration == null) { 1083 return ewns.wns.wifiConfiguration.enterpriseConfig != null 1084 && ewns.wns.wifiConfiguration.enterpriseConfig.isAuthenticationSimBased() 1085 && !ewns.wns.wifiConfiguration.enterpriseConfig.isEapMethodServerCertUsed(); 1086 } else { 1087 return ewns.wns.passpointConfiguration.getCredential().getSimCredential() != null; 1088 } 1089 } 1090 checkNetworkSuggestionsNoNulls(List<WifiNetworkSuggestion> networkSuggestions)1091 private boolean checkNetworkSuggestionsNoNulls(List<WifiNetworkSuggestion> networkSuggestions) { 1092 for (WifiNetworkSuggestion wns : networkSuggestions) { 1093 if (wns == null || wns.wifiConfiguration == null) { 1094 return false; 1095 } 1096 } 1097 return true; 1098 } 1099 validateNetworkSuggestions( List<WifiNetworkSuggestion> networkSuggestions, String packageName, int uid)1100 private boolean validateNetworkSuggestions( 1101 List<WifiNetworkSuggestion> networkSuggestions, String packageName, int uid) { 1102 if (!checkNetworkSuggestionsNoNulls(networkSuggestions)) { 1103 return false; 1104 } 1105 1106 long supportedFeatures = mWifiInjector.getActiveModeWarden() 1107 .getPrimaryClientModeManager().getSupportedFeatures(); 1108 1109 for (WifiNetworkSuggestion wns : networkSuggestions) { 1110 if (wns.passpointConfiguration == null) { 1111 WifiConfiguration config = wns.wifiConfiguration; 1112 if (!WifiConfigurationUtil.validate(config, supportedFeatures, 1113 WifiConfigurationUtil.VALIDATE_FOR_ADD)) { 1114 return false; 1115 } 1116 if (config.macRandomizationSetting != WifiConfiguration.RANDOMIZATION_PERSISTENT 1117 && config.macRandomizationSetting 1118 != WifiConfiguration.RANDOMIZATION_NON_PERSISTENT) { 1119 Log.w(TAG, "MAC randomization setting is invalid. Automatically setting" 1120 + " config to use persistent random MAC address."); 1121 config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT; 1122 } 1123 if (config.isEnterprise()) { 1124 final WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig; 1125 if (enterpriseConfig.isEapMethodServerCertUsed() 1126 && !enterpriseConfig.isMandatoryParameterSetForServerCertValidation()) { 1127 Log.e(TAG, "Insecure enterprise suggestion is invalid."); 1128 return false; 1129 } 1130 final String alias = enterpriseConfig.getClientKeyPairAliasInternal(); 1131 if (alias != null && !mWifiKeyStore.validateKeyChainAlias(alias, uid)) { 1132 Log.e(TAG, "Invalid client key pair KeyChain alias: " + alias); 1133 return false; 1134 } 1135 } 1136 1137 } else { 1138 if (!wns.passpointConfiguration.validate()) { 1139 EventLog.writeEvent(0x534e4554, "245299920", uid, 1140 "Trying to add invalid passpoint suggestion"); 1141 return false; 1142 } 1143 if (!wns.passpointConfiguration.isMacRandomizationEnabled()) { 1144 Log.w(TAG, "MAC randomization must be enabled on Passpoint suggestion." 1145 + " Defaulting to use persistent MAC randomization for invalid" 1146 + " configuration."); 1147 wns.passpointConfiguration.setMacRandomizationEnabled(true); 1148 wns.passpointConfiguration.setNonPersistentMacRandomizationEnabled(false); 1149 } 1150 } 1151 if (!isAppWorkingAsCrossCarrierProvider(packageName) 1152 && !isValidCarrierMergedNetworkSuggestion(wns)) { 1153 Log.e(TAG, "Merged carrier network must be a metered, enterprise config with a " 1154 + "valid subscription Id"); 1155 return false; 1156 } 1157 if (!SdkLevel.isAtLeastS()) { 1158 if (wns.wifiConfiguration.oemPaid) { 1159 Log.e(TAG, "OEM paid suggestions are only allowed from Android S."); 1160 return false; 1161 } 1162 if (wns.wifiConfiguration.oemPrivate) { 1163 Log.e(TAG, "OEM private suggestions are only allowed from Android S."); 1164 return false; 1165 } 1166 if (wns.wifiConfiguration.subscriptionId 1167 != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 1168 Log.e(TAG, "Setting Subscription Id is only allowed from Android S."); 1169 return false; 1170 } 1171 if (wns.priorityGroup != 0) { 1172 Log.e(TAG, "Setting Priority group is only allowed from Android S."); 1173 return false; 1174 } 1175 if (wns.wifiConfiguration.carrierMerged) { 1176 Log.e(TAG, "Setting carrier merged network is only allowed from Android S."); 1177 return false; 1178 } 1179 } 1180 if (!SdkLevel.isAtLeastT()) { 1181 if (wns.wifiConfiguration.getSubscriptionGroup() != null) { 1182 Log.e(TAG, "Setting subscription group is only allowed from Android T."); 1183 return false; 1184 } 1185 } 1186 if (wns.wifiConfiguration.getSubscriptionGroup() != null 1187 && wns.wifiConfiguration.subscriptionId 1188 != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 1189 Log.e(TAG, "Setting both subscription group and subscription id are not " 1190 + "allowed."); 1191 return false; 1192 } 1193 } 1194 return true; 1195 } 1196 isValidCarrierMergedNetworkSuggestion(WifiNetworkSuggestion wns)1197 private boolean isValidCarrierMergedNetworkSuggestion(WifiNetworkSuggestion wns) { 1198 if (!wns.wifiConfiguration.carrierMerged) { 1199 // Not carrier merged. 1200 return true; 1201 } 1202 if (!wns.wifiConfiguration.isEnterprise() && wns.passpointConfiguration == null) { 1203 // Carrier merged network must be a enterprise network. 1204 return false; 1205 } 1206 if (!WifiConfiguration.isMetered(wns.wifiConfiguration, null)) { 1207 // Carrier merged network must be metered. 1208 return false; 1209 } 1210 if (wns.wifiConfiguration.subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID 1211 && wns.wifiConfiguration.getSubscriptionGroup() == null) { 1212 // Carrier merged network must have a valid subscription Id. 1213 return false; 1214 } 1215 return true; 1216 } 1217 validateCarrierNetworkSuggestions( List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName, int provisionerCarrierId)1218 private boolean validateCarrierNetworkSuggestions( 1219 List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName, 1220 int provisionerCarrierId) { 1221 boolean isAppWorkingAsCrossCarrierProvider = isAppWorkingAsCrossCarrierProvider( 1222 packageName); 1223 boolean isCrossCarrierProvisioner = 1224 mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(uid) 1225 || isAppWorkingAsCrossCarrierProvider; 1226 1227 for (WifiNetworkSuggestion suggestion : networkSuggestions) { 1228 WifiConfiguration wifiConfiguration = suggestion.wifiConfiguration; 1229 PasspointConfiguration passpointConfiguration = suggestion.passpointConfiguration; 1230 if (wifiConfiguration.carrierMerged && !areCarrierMergedSuggestionsAllowed( 1231 wifiConfiguration, packageName)) { 1232 // Carrier must be explicitly configured as merged carrier offload enabled 1233 return false; 1234 } 1235 if (!isCrossCarrierProvisioner && provisionerCarrierId 1236 == TelephonyManager.UNKNOWN_CARRIER_ID) { 1237 // If an app doesn't have carrier privileges or carrier provisioning permission, 1238 // suggests SIM-based network, sets CarrierId and sets SubscriptionId are illegal. 1239 if (wifiConfiguration.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) { 1240 return false; 1241 } 1242 if (wifiConfiguration.subscriptionId 1243 != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 1244 return false; 1245 } 1246 if (passpointConfiguration == null) { 1247 if (wifiConfiguration.enterpriseConfig != null 1248 && wifiConfiguration.enterpriseConfig.isAuthenticationSimBased()) { 1249 return false; 1250 } 1251 } else { 1252 if (passpointConfiguration.getCredential() != null 1253 && passpointConfiguration.getCredential().getSimCredential() != null) { 1254 return false; 1255 } 1256 } 1257 } else { 1258 int carrierId = isCrossCarrierProvisioner ? wifiConfiguration.carrierId 1259 : provisionerCarrierId; 1260 int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 1261 if (wifiConfiguration.getSubscriptionGroup() != null) { 1262 subId = mWifiCarrierInfoManager.getActiveSubscriptionIdInGroup( 1263 wifiConfiguration.getSubscriptionGroup()); 1264 } else { 1265 subId = wifiConfiguration.subscriptionId; 1266 } 1267 if (!mWifiCarrierInfoManager 1268 .isSubIdMatchingCarrierId(subId, carrierId)) { 1269 Log.e(TAG, "Subscription ID doesn't match the carrier. CarrierId:" 1270 + carrierId + ", subscriptionId:" + subId + ", NetworkSuggestion:" 1271 + suggestion); 1272 return false; 1273 } 1274 } 1275 } 1276 return true; 1277 } 1278 stopTrackingAppOpsChange(@onNull String packageName)1279 private void stopTrackingAppOpsChange(@NonNull String packageName) { 1280 AppOpsChangedListener appOpsChangedListener = 1281 mAppOpsChangedListenerPerApp.remove(packageName); 1282 if (appOpsChangedListener == null) { 1283 Log.wtf(TAG, "No app ops listener found for " + packageName); 1284 return; 1285 } 1286 mAppOps.stopWatchingMode(appOpsChangedListener); 1287 } 1288 1289 /** 1290 * Remove provided list from that App active list. If provided list is empty, will remove all. 1291 * Will disconnect network if current connected network is in the remove list. 1292 */ removeInternal( @onNull Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions, @NonNull String packageName, @NonNull PerAppInfo perAppInfo, @WifiManager.ActionAfterRemovingSuggestion int action)1293 private void removeInternal( 1294 @NonNull Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions, 1295 @NonNull String packageName, 1296 @NonNull PerAppInfo perAppInfo, @WifiManager.ActionAfterRemovingSuggestion int action) { 1297 // Get internal suggestions 1298 Set<ExtendedWifiNetworkSuggestion> removingExtSuggestions = 1299 new HashSet<>(perAppInfo.extNetworkSuggestions.values()); 1300 if (!extNetworkSuggestions.isEmpty()) { 1301 // Keep the internal suggestions need to remove. 1302 removingExtSuggestions.retainAll(extNetworkSuggestions); 1303 perAppInfo.extNetworkSuggestions.values().removeAll(extNetworkSuggestions); 1304 } else { 1305 // empty list is used to clear everything for the app. Store a copy for use below. 1306 perAppInfo.extNetworkSuggestions.clear(); 1307 } 1308 if (perAppInfo.extNetworkSuggestions.isEmpty()) { 1309 // Note: We don't remove the app entry even if there is no active suggestions because 1310 // we want to keep the notification state for all apps that have ever provided 1311 // suggestions. 1312 if (mVerboseLoggingEnabled) Log.v(TAG, "No active suggestions for " + packageName); 1313 // Stop tracking app-op changes from the app if they don't have active suggestions. 1314 stopTrackingAppOpsChange(packageName); 1315 } 1316 // Clear the cache. 1317 WifiConfiguration connected = mWifiInjector.getActiveModeWarden() 1318 .getPrimaryClientModeManager().getConnectedWifiConfiguration(); 1319 List<WifiNetworkSuggestion> removingSuggestions = new ArrayList<>(); 1320 for (ExtendedWifiNetworkSuggestion ewns : removingExtSuggestions) { 1321 removeNetworkSuggestionCache(ewns); 1322 removingSuggestions.add(ewns.wns); 1323 WifiConfiguration removing = ewns 1324 .createInternalWifiConfiguration(mWifiCarrierInfoManager); 1325 WifiConfiguration cached = mWifiConfigManager.getConfiguredNetwork( 1326 removing.getProfileKey()); 1327 if (connected != null && cached != null && cached.networkId == connected.networkId 1328 && action == ACTION_REMOVE_SUGGESTION_LINGER) { 1329 mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager() 1330 .setShouldReduceNetworkScore(true); 1331 // Execute when linger time out clean up the cache in WifiConfigManager. 1332 mHandler.postDelayed(() -> removeSuggestionFromWifiConfigManager(ewns), 1333 getLingerDelayMs()); 1334 } else { 1335 // Remove the config from WifiConfigManager. If current connected suggestion is 1336 // remove, would trigger a disconnect. 1337 mWifiConfigManager.removeSuggestionConfiguredNetwork(removing); 1338 } 1339 } 1340 for (OnSuggestionUpdateListener listener : mListeners) { 1341 listener.onSuggestionsRemoved(removingSuggestions); 1342 } 1343 } 1344 removeNetworkSuggestionCache(ExtendedWifiNetworkSuggestion ewns)1345 private void removeNetworkSuggestionCache(ExtendedWifiNetworkSuggestion ewns) { 1346 if (ewns.wns.passpointConfiguration != null) { 1347 // Clear the Passpoint config. 1348 mWifiInjector.getPasspointManager().removeProvider( 1349 ewns.perAppInfo.uid, 1350 false, 1351 ewns.wns.passpointConfiguration.getUniqueId(), null); 1352 removeFromPassPointInfoMap(ewns); 1353 } else { 1354 if (ewns.wns.wifiConfiguration.isEnterprise()) { 1355 mWifiKeyStore.removeKeys(ewns.wns.wifiConfiguration.enterpriseConfig, false); 1356 } 1357 removeFromScanResultMatchInfoMapAndRemoveRelatedScoreCard(ewns, true); 1358 mWifiConfigManager.removeConnectChoiceFromAllNetworks(ewns 1359 .createInternalWifiConfiguration(mWifiCarrierInfoManager) 1360 .getProfileKey()); 1361 } 1362 } 1363 removeSuggestionFromWifiConfigManager( ExtendedWifiNetworkSuggestion extendedWifiNetworkSuggestion)1364 private void removeSuggestionFromWifiConfigManager( 1365 ExtendedWifiNetworkSuggestion extendedWifiNetworkSuggestion) { 1366 PerAppInfo perAppInfo = extendedWifiNetworkSuggestion.perAppInfo; 1367 if (perAppInfo.extNetworkSuggestions.containsValue(extendedWifiNetworkSuggestion)) { 1368 // If the suggestion is added by app again, do not remove it from WifiConfigManager. 1369 return; 1370 } 1371 mWifiConfigManager.removeSuggestionConfiguredNetwork(extendedWifiNetworkSuggestion 1372 .createInternalWifiConfiguration(mWifiCarrierInfoManager)); 1373 } 1374 1375 /** 1376 * Remove the provided list of network suggestions from the corresponding app's active list. 1377 */ remove( List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName, @WifiManager.ActionAfterRemovingSuggestion int action)1378 public @WifiManager.NetworkSuggestionsStatusCode int remove( 1379 List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName, 1380 @WifiManager.ActionAfterRemovingSuggestion int action) { 1381 if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(uid)) { 1382 Log.e(TAG, "UID " + uid + " not visible to the current user"); 1383 return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL; 1384 } 1385 if (!mUserDataLoaded) { 1386 Log.e(TAG, "Remove Network suggestion before boot complete is not allowed."); 1387 return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL; 1388 } 1389 if (networkSuggestions == null) { 1390 Log.w(TAG, "Null list of network suggestions for " + packageName + ". Ignoring"); 1391 return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; 1392 } 1393 if (mVerboseLoggingEnabled) { 1394 Log.v(TAG, "Removing " + networkSuggestions.size() + " networks from " + packageName); 1395 } 1396 if (!checkNetworkSuggestionsNoNulls(networkSuggestions)) { 1397 Log.e(TAG, "Null in suggestion remove from app: " + packageName); 1398 return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID; 1399 } 1400 PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName); 1401 if (perAppInfo == null) { 1402 Log.e(TAG, "Failed to remove network suggestions for " + packageName 1403 + ". No network suggestions found"); 1404 return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID; 1405 } 1406 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = 1407 convertToExtendedWnsSet(networkSuggestions, perAppInfo); 1408 Set<Integer> keySet = extNetworkSuggestions 1409 .stream() 1410 .map(ExtendedWifiNetworkSuggestion::hashCode) 1411 .collect(Collectors.toSet()); 1412 // check if all the request network suggestions are present in the active list. 1413 if (!extNetworkSuggestions.isEmpty() 1414 && !perAppInfo.extNetworkSuggestions.keySet().containsAll(keySet)) { 1415 Log.e(TAG, "Failed to remove network suggestions for " + packageName 1416 + ". Network suggestions not found in active network suggestions"); 1417 return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID; 1418 } 1419 removeInternal(extNetworkSuggestions, packageName, perAppInfo, action); 1420 saveToStore(); 1421 mWifiMetrics.incrementNetworkSuggestionApiNumModification(); 1422 mWifiMetrics.noteNetworkSuggestionApiListSizeHistogram(getAllMaxSizes()); 1423 return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; 1424 } 1425 1426 /** 1427 * Remove all tracking of the app that has been uninstalled. 1428 */ removeApp(@onNull String packageName)1429 public void removeApp(@NonNull String packageName) { 1430 PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName); 1431 if (perAppInfo == null) return; 1432 removeInternal(List.of(), packageName, perAppInfo, ACTION_REMOVE_SUGGESTION_DISCONNECT); 1433 // Remove the package fully from the internal database. 1434 mActiveNetworkSuggestionsPerApp.remove(packageName); 1435 RemoteCallbackList<ISuggestionConnectionStatusListener> listenerTracker = 1436 mSuggestionStatusListenerPerApp.remove(packageName); 1437 if (listenerTracker != null) listenerTracker.kill(); 1438 saveToStore(); 1439 Log.i(TAG, "Removed " + packageName); 1440 } 1441 1442 /** 1443 * Get all network suggestion for target App 1444 * @return List of WifiNetworkSuggestions 1445 */ get(@onNull String packageName, int uid)1446 public @NonNull List<WifiNetworkSuggestion> get(@NonNull String packageName, int uid) { 1447 List<WifiNetworkSuggestion> networkSuggestionList = new ArrayList<>(); 1448 if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(uid)) { 1449 Log.e(TAG, "UID " + uid + " not visible to the current user"); 1450 return networkSuggestionList; 1451 } 1452 if (!mUserDataLoaded) { 1453 Log.e(TAG, "Get Network suggestion before boot complete is not allowed."); 1454 return networkSuggestionList; 1455 } 1456 PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName); 1457 // if App never suggested return empty list. 1458 if (perAppInfo == null) return networkSuggestionList; 1459 for (ExtendedWifiNetworkSuggestion extendedSuggestion : perAppInfo.extNetworkSuggestions 1460 .values()) { 1461 networkSuggestionList.add(extendedSuggestion.wns); 1462 } 1463 return networkSuggestionList; 1464 } 1465 1466 /** 1467 * Clear all internal state (for network settings reset). 1468 */ clear()1469 public void clear() { 1470 Iterator<Map.Entry<String, PerAppInfo>> iter = 1471 mActiveNetworkSuggestionsPerApp.entrySet().iterator(); 1472 while (iter.hasNext()) { 1473 Map.Entry<String, PerAppInfo> entry = iter.next(); 1474 removeInternal(List.of(), entry.getKey(), entry.getValue(), 1475 ACTION_REMOVE_SUGGESTION_DISCONNECT); 1476 iter.remove(); 1477 } 1478 mSuggestionStatusListenerPerApp.clear(); 1479 mSuggestionUserApprovalStatusListenerPerApp.clear(); 1480 resetNotification(); 1481 saveToStore(); 1482 Log.i(TAG, "Cleared all internal state"); 1483 } 1484 1485 /** 1486 * Check if network suggestions are enabled or disabled for the app. 1487 */ hasUserApprovedForApp(String packageName)1488 public boolean hasUserApprovedForApp(String packageName) { 1489 PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName); 1490 if (perAppInfo == null) return false; 1491 1492 return perAppInfo.hasUserApproved; 1493 } 1494 1495 /** 1496 * Enable or Disable network suggestions for the app. 1497 */ setHasUserApprovedForApp(boolean approved, int uid, String packageName)1498 public void setHasUserApprovedForApp(boolean approved, int uid, String packageName) { 1499 PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName); 1500 if (perAppInfo == null) return; 1501 1502 if (mVerboseLoggingEnabled) { 1503 Log.v(TAG, "Setting the app " + packageName 1504 + (approved ? " approved" : " not approved")); 1505 } 1506 perAppInfo.hasUserApproved = approved; 1507 onSuggestionUserApprovalStatusChanged(uid, packageName); 1508 saveToStore(); 1509 } 1510 1511 /** 1512 * When user approve the IMSI protection exemption for carrier, restore the initial auto join 1513 * configure. If user already change it to enabled, keep that choice. 1514 */ restoreInitialAutojoinForCarrierId(int carrierId)1515 private void restoreInitialAutojoinForCarrierId(int carrierId) { 1516 for (PerAppInfo appInfo : mActiveNetworkSuggestionsPerApp.values()) { 1517 for (ExtendedWifiNetworkSuggestion ewns : appInfo.extNetworkSuggestions.values()) { 1518 if (!(isSimBasedPhase1Suggestion(ewns) 1519 && getCarrierIdFromSuggestion(ewns) == carrierId)) { 1520 continue; 1521 } 1522 if (mVerboseLoggingEnabled) { 1523 Log.v(TAG, "Restore auto-join for suggestion: " + ewns); 1524 } 1525 ewns.isAutojoinEnabled |= ewns.wns.isInitialAutoJoinEnabled; 1526 // Restore passpoint provider auto join. 1527 if (ewns.wns.passpointConfiguration != null) { 1528 mWifiInjector.getPasspointManager() 1529 .enableAutojoin(ewns.wns.passpointConfiguration.getUniqueId(), 1530 null, ewns.isAutojoinEnabled); 1531 } else { 1532 // Update WifiConfigManager to sync auto-join. 1533 updateWifiConfigInWcmIfPresent(ewns.createInternalWifiConfiguration( 1534 mWifiCarrierInfoManager), 1535 ewns.perAppInfo.uid, ewns.perAppInfo.packageName); 1536 } 1537 } 1538 } 1539 saveToStore(); 1540 } 1541 1542 /** 1543 * Returns a set of all network suggestions across all apps. 1544 */ 1545 @VisibleForTesting getAllNetworkSuggestions()1546 public Set<WifiNetworkSuggestion> getAllNetworkSuggestions() { 1547 return mActiveNetworkSuggestionsPerApp.values() 1548 .stream() 1549 .flatMap(e -> convertToWnsSet(e.extNetworkSuggestions.values()) 1550 .stream()) 1551 .collect(Collectors.toSet()); 1552 } 1553 1554 /** 1555 * Returns a set of all network suggestions across all apps that have been approved by user. 1556 */ getAllApprovedNetworkSuggestions()1557 public Set<WifiNetworkSuggestion> getAllApprovedNetworkSuggestions() { 1558 return mActiveNetworkSuggestionsPerApp.values() 1559 .stream() 1560 .filter(e -> e.isApproved()) 1561 .flatMap(e -> convertToWnsSet(e.extNetworkSuggestions.values()) 1562 .stream()) 1563 .collect(Collectors.toSet()); 1564 } 1565 1566 /** 1567 * Get all user approved, non-passpoint networks from suggestion. 1568 */ getAllScanOptimizationSuggestionNetworks()1569 public List<WifiConfiguration> getAllScanOptimizationSuggestionNetworks() { 1570 List<WifiConfiguration> networks = new ArrayList<>(); 1571 for (PerAppInfo info : mActiveNetworkSuggestionsPerApp.values()) { 1572 if (!info.isApproved()) { 1573 continue; 1574 } 1575 for (ExtendedWifiNetworkSuggestion ewns : info.extNetworkSuggestions.values()) { 1576 if (ewns.wns.getPasspointConfig() != null) { 1577 continue; 1578 } 1579 WifiConfiguration network = mWifiConfigManager 1580 .getConfiguredNetwork(ewns.wns.getWifiConfiguration() 1581 .getProfileKey()); 1582 if (network == null) { 1583 network = ewns.createInternalWifiConfiguration(mWifiCarrierInfoManager); 1584 } 1585 networks.add(network); 1586 } 1587 } 1588 return networks; 1589 } 1590 getAllMaxSizes()1591 private List<Integer> getAllMaxSizes() { 1592 return mActiveNetworkSuggestionsPerApp.values() 1593 .stream() 1594 .map(e -> e.maxSize) 1595 .collect(Collectors.toList()); 1596 } 1597 getPrivateBroadcast(@onNull String action, @NonNull Pair<String, String> extra1, @NonNull Pair<String, Integer> extra2)1598 private PendingIntent getPrivateBroadcast(@NonNull String action, 1599 @NonNull Pair<String, String> extra1, @NonNull Pair<String, Integer> extra2) { 1600 Intent intent = new Intent(action) 1601 .setPackage(mContext.getServiceWifiPackageName()) 1602 .putExtra(extra1.first, extra1.second) 1603 .putExtra(extra2.first, extra2.second); 1604 return mFrameworkFacade.getBroadcast(mContext, 0, intent, 1605 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); 1606 } 1607 1608 /** 1609 * Check if the request came from foreground app. 1610 */ isSuggestionFromForegroundApp(@onNull String packageName)1611 private boolean isSuggestionFromForegroundApp(@NonNull String packageName) { 1612 try { 1613 return mActivityManager.getPackageImportance(packageName) 1614 <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; 1615 } catch (SecurityException e) { 1616 Log.e(TAG, "Failed to check the app state", e); 1617 return false; 1618 } 1619 } 1620 sendUserApprovalDialog(@onNull String packageName, int uid)1621 private void sendUserApprovalDialog(@NonNull String packageName, int uid) { 1622 CharSequence appName = mFrameworkFacade.getAppName(mContext, packageName, uid); 1623 mWifiInjector.getWifiDialogManager().createSimpleDialog( 1624 mResources.getString(R.string.wifi_suggestion_title), 1625 mResources.getString(R.string.wifi_suggestion_content, appName), 1626 mResources.getString(R.string.wifi_suggestion_action_allow_app), 1627 mResources.getString(R.string.wifi_suggestion_action_disallow_app), 1628 null /* neutralButtonText */, 1629 new WifiDialogManager.SimpleDialogCallback() { 1630 @Override 1631 public void onPositiveButtonClicked() { 1632 handleUserAllowAction(uid, packageName); 1633 } 1634 1635 @Override 1636 public void onNegativeButtonClicked() { 1637 handleUserDisallowAction(uid, packageName); 1638 } 1639 1640 @Override 1641 public void onNeutralButtonClicked() { 1642 // Not used. 1643 handleUserDismissAction(); 1644 } 1645 1646 @Override 1647 public void onCancelled() { 1648 handleUserDismissAction(); 1649 } 1650 }, 1651 new WifiThreadRunner(mHandler)).launchDialog(); 1652 mIsLastUserApprovalUiDialog = true; 1653 } 1654 sendUserApprovalNotification(@onNull String packageName, int uid)1655 private void sendUserApprovalNotification(@NonNull String packageName, int uid) { 1656 Notification.Action userAllowAppNotificationAction = 1657 new Notification.Action.Builder(null, 1658 mResources.getText(R.string.wifi_suggestion_action_allow_app), 1659 getPrivateBroadcast(NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION, 1660 Pair.create(EXTRA_PACKAGE_NAME, packageName), 1661 Pair.create(EXTRA_UID, uid))) 1662 .build(); 1663 Notification.Action userDisallowAppNotificationAction = 1664 new Notification.Action.Builder(null, 1665 mResources.getText(R.string.wifi_suggestion_action_disallow_app), 1666 getPrivateBroadcast(NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION, 1667 Pair.create(EXTRA_PACKAGE_NAME, packageName), 1668 Pair.create(EXTRA_UID, uid))) 1669 .build(); 1670 1671 CharSequence appName = mFrameworkFacade.getAppName(mContext, packageName, uid); 1672 Notification notification = mFrameworkFacade.makeNotificationBuilder( 1673 mContext, WifiService.NOTIFICATION_NETWORK_STATUS) 1674 .setSmallIcon(Icon.createWithResource(mContext.getWifiOverlayApkPkgName(), 1675 com.android.wifi.resources.R.drawable.stat_notify_wifi_in_range)) 1676 .setTicker(mResources.getString(R.string.wifi_suggestion_title)) 1677 .setContentTitle(mResources.getString(R.string.wifi_suggestion_title)) 1678 .setStyle(new Notification.BigTextStyle() 1679 .bigText(mResources.getString(R.string.wifi_suggestion_content, appName))) 1680 .setDeleteIntent(getPrivateBroadcast(NOTIFICATION_USER_DISMISSED_INTENT_ACTION, 1681 Pair.create(EXTRA_PACKAGE_NAME, packageName), Pair.create(EXTRA_UID, uid))) 1682 .setShowWhen(false) 1683 .setLocalOnly(true) 1684 .setColor(mResources.getColor(android.R.color.system_notification_accent_color, 1685 mContext.getTheme())) 1686 .addAction(userAllowAppNotificationAction) 1687 .addAction(userDisallowAppNotificationAction) 1688 .setTimeoutAfter(NOTIFICATION_EXPIRY_MILLS) 1689 .build(); 1690 1691 // Post the notification. 1692 mNotificationManager.notify(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE, notification); 1693 mNotificationUpdateTime = mClock.getElapsedSinceBootMillis() 1694 + NOTIFICATION_UPDATE_DELAY_MILLS; 1695 mIsLastUserApprovalUiDialog = false; 1696 } 1697 1698 /** 1699 * Send user approval notification if the app is not approved 1700 * @param packageName app package name 1701 * @param uid app UID 1702 * @return true if app is not approved and send notification. 1703 */ sendUserApprovalNotificationIfNotApproved( @onNull String packageName, @NonNull int uid)1704 private boolean sendUserApprovalNotificationIfNotApproved( 1705 @NonNull String packageName, @NonNull int uid) { 1706 if (!mActiveNetworkSuggestionsPerApp.containsKey(packageName)) { 1707 Log.wtf(TAG, "AppInfo is missing for " + packageName); 1708 return false; 1709 } 1710 if (mActiveNetworkSuggestionsPerApp.get(packageName).hasUserApproved) { 1711 return false; // already approved. 1712 } 1713 1714 if (mNotificationUpdateTime > mClock.getElapsedSinceBootMillis()) { 1715 return false; // Active notification is still available, do not update. 1716 } 1717 Log.i(TAG, "Sending user approval notification for " + packageName); 1718 sendUserApprovalNotification(packageName, uid); 1719 return true; 1720 } 1721 1722 private @Nullable Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForScanResultMatchInfo( @onNull ScanResultMatchInfo scanResultMatchInfo, @Nullable MacAddress bssid)1723 getNetworkSuggestionsForScanResultMatchInfo( 1724 @NonNull ScanResultMatchInfo scanResultMatchInfo, @Nullable MacAddress bssid) { 1725 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = new HashSet<>(); 1726 if (bssid != null) { 1727 Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsWithBssid = 1728 mActiveScanResultMatchInfoWithBssid.get( 1729 Pair.create(scanResultMatchInfo, bssid)); 1730 if (matchingExtNetworkSuggestionsWithBssid != null) { 1731 extNetworkSuggestions.addAll(matchingExtNetworkSuggestionsWithBssid); 1732 } 1733 } 1734 Set<ExtendedWifiNetworkSuggestion> matchingNetworkSuggestionsWithNoBssid = 1735 mActiveScanResultMatchInfoWithNoBssid.get(scanResultMatchInfo); 1736 if (matchingNetworkSuggestionsWithNoBssid != null) { 1737 extNetworkSuggestions.addAll(matchingNetworkSuggestionsWithNoBssid); 1738 } 1739 if (extNetworkSuggestions.isEmpty()) { 1740 return null; 1741 } 1742 return extNetworkSuggestions; 1743 } 1744 getNetworkSuggestionsForFqdnMatch( @ullable String fqdn)1745 private @Nullable Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForFqdnMatch( 1746 @Nullable String fqdn) { 1747 if (TextUtils.isEmpty(fqdn)) { 1748 return null; 1749 } 1750 return mPasspointInfo.get(fqdn); 1751 } 1752 1753 /** 1754 * Returns a set of all network suggestions matching the provided FQDN. 1755 */ getNetworkSuggestionsForFqdn(String fqdn)1756 public @NonNull Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForFqdn(String fqdn) { 1757 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = 1758 getNetworkSuggestionsForFqdnMatch(fqdn); 1759 if (extNetworkSuggestions == null) { 1760 return Set.of(); 1761 } 1762 Set<ExtendedWifiNetworkSuggestion> approvedExtNetworkSuggestions = new HashSet<>(); 1763 for (ExtendedWifiNetworkSuggestion ewns : extNetworkSuggestions) { 1764 if (!ewns.perAppInfo.isApproved()) { 1765 sendUserApprovalNotificationIfNotApproved(ewns.perAppInfo.packageName, 1766 ewns.perAppInfo.uid); 1767 continue; 1768 } 1769 if (ewns.wns.wifiConfiguration.carrierMerged && !areCarrierMergedSuggestionsAllowed( 1770 ewns.wns.wifiConfiguration, ewns.perAppInfo.packageName)) { 1771 continue; 1772 } 1773 if (isSimBasedPhase1Suggestion(ewns)) { 1774 mWifiCarrierInfoManager.sendImsiProtectionExemptionNotificationIfRequired( 1775 getCarrierIdFromSuggestion(ewns)); 1776 } 1777 approvedExtNetworkSuggestions.add(ewns); 1778 } 1779 1780 if (approvedExtNetworkSuggestions.isEmpty()) { 1781 return Set.of(); 1782 } 1783 if (mVerboseLoggingEnabled) { 1784 Log.v(TAG, "getNetworkSuggestionsForFqdn Found " 1785 + approvedExtNetworkSuggestions + " for " + fqdn); 1786 } 1787 return approvedExtNetworkSuggestions; 1788 } 1789 1790 /** 1791 * Returns a set of all network suggestions matching the provided scan detail. 1792 */ getNetworkSuggestionsForScanDetail( @onNull ScanDetail scanDetail)1793 public @NonNull Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForScanDetail( 1794 @NonNull ScanDetail scanDetail) { 1795 ScanResult scanResult = scanDetail.getScanResult(); 1796 if (scanResult == null) { 1797 Log.e(TAG, "No scan result found in scan detail"); 1798 return Set.of(); 1799 } 1800 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = null; 1801 try { 1802 ScanResultMatchInfo scanResultMatchInfo = 1803 ScanResultMatchInfo.fromScanResult(scanResult); 1804 extNetworkSuggestions = getNetworkSuggestionsForScanResultMatchInfo( 1805 scanResultMatchInfo, MacAddress.fromString(scanResult.BSSID)); 1806 } catch (IllegalArgumentException e) { 1807 Log.e(TAG, "Failed to lookup network from scan result match info map", e); 1808 } 1809 if (extNetworkSuggestions == null) { 1810 return Set.of(); 1811 } 1812 Set<ExtendedWifiNetworkSuggestion> approvedExtNetworkSuggestions = new HashSet<>(); 1813 for (ExtendedWifiNetworkSuggestion ewns : extNetworkSuggestions) { 1814 if (!ewns.perAppInfo.isApproved()) { 1815 sendUserApprovalNotificationIfNotApproved(ewns.perAppInfo.packageName, 1816 ewns.perAppInfo.uid); 1817 continue; 1818 } 1819 if (ewns.wns.wifiConfiguration.carrierMerged && !areCarrierMergedSuggestionsAllowed( 1820 ewns.wns.wifiConfiguration, ewns.perAppInfo.packageName)) { 1821 continue; 1822 } 1823 if (isSimBasedPhase1Suggestion(ewns)) { 1824 mWifiCarrierInfoManager.sendImsiProtectionExemptionNotificationIfRequired( 1825 getCarrierIdFromSuggestion(ewns)); 1826 } 1827 approvedExtNetworkSuggestions.add(ewns); 1828 } 1829 1830 if (approvedExtNetworkSuggestions.isEmpty()) { 1831 return Set.of(); 1832 } 1833 if (mVerboseLoggingEnabled) { 1834 Log.v(TAG, "getNetworkSuggestionsForScanDetail Found " 1835 + approvedExtNetworkSuggestions + " for " + scanResult.SSID 1836 + "[" + scanResult.capabilities + "]"); 1837 } 1838 return approvedExtNetworkSuggestions; 1839 } 1840 1841 /** 1842 * Returns a set of all network suggestions matching the provided the WifiConfiguration. 1843 */ getNetworkSuggestionsForWifiConfiguration( @onNull WifiConfiguration wifiConfiguration, @Nullable String bssid)1844 public @Nullable Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForWifiConfiguration( 1845 @NonNull WifiConfiguration wifiConfiguration, @Nullable String bssid) { 1846 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = null; 1847 if (wifiConfiguration.isPasspoint()) { 1848 extNetworkSuggestions = getNetworkSuggestionsForFqdnMatch(wifiConfiguration.FQDN); 1849 } else { 1850 try { 1851 ScanResultMatchInfo scanResultMatchInfo = 1852 ScanResultMatchInfo.fromWifiConfiguration(wifiConfiguration); 1853 extNetworkSuggestions = getNetworkSuggestionsForScanResultMatchInfo( 1854 scanResultMatchInfo, bssid == null ? null : MacAddress.fromString(bssid)); 1855 } catch (IllegalArgumentException e) { 1856 Log.e(TAG, "Failed to lookup network from scan result match info map", e); 1857 } 1858 } 1859 if (extNetworkSuggestions == null || extNetworkSuggestions.isEmpty()) { 1860 return null; 1861 } 1862 Set<ExtendedWifiNetworkSuggestion> approvedExtNetworkSuggestions = 1863 extNetworkSuggestions 1864 .stream() 1865 .filter(n -> n.perAppInfo.isApproved()) 1866 .collect(Collectors.toSet()); 1867 if (approvedExtNetworkSuggestions.isEmpty()) { 1868 return null; 1869 } 1870 if (mVerboseLoggingEnabled) { 1871 Log.v(TAG, "getNetworkSuggestionsForWifiConfiguration Found " 1872 + approvedExtNetworkSuggestions + " for " + wifiConfiguration.SSID 1873 + wifiConfiguration.FQDN + "[" + wifiConfiguration.allowedKeyManagement + "]"); 1874 } 1875 return approvedExtNetworkSuggestions; 1876 } 1877 1878 /** 1879 * Retrieve the WifiConfigurations for all matched suggestions which allow user manually connect 1880 * and user already approved for non-open networks. 1881 */ getWifiConfigForMatchedNetworkSuggestionsSharedWithUser( List<ScanResult> scanResults)1882 public @NonNull List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser( 1883 List<ScanResult> scanResults) { 1884 // Create a temporary look-up table. 1885 // As they are all single type configurations, they should have unique keys. 1886 Map<String, WifiConfiguration> wifiConfigMap = new HashMap<>(); 1887 WifiConfigurationUtil.convertMultiTypeConfigsToLegacyConfigs( 1888 mWifiConfigManager.getConfiguredNetworks()) 1889 .forEach(c -> wifiConfigMap.put(c.getProfileKey(), c)); 1890 1891 // Create a HashSet to avoid return multiple result for duplicate ScanResult. 1892 Set<String> networkKeys = new HashSet<>(); 1893 List<WifiConfiguration> sharedWifiConfigs = new ArrayList<>(); 1894 for (ScanResult scanResult : scanResults) { 1895 ScanResultMatchInfo scanResultMatchInfo = 1896 ScanResultMatchInfo.fromScanResult(scanResult); 1897 if (scanResultMatchInfo.securityParamsList.size() == 0) continue; 1898 // Only filter legacy Open network. 1899 if (scanResultMatchInfo.securityParamsList.size() == 1 1900 && scanResultMatchInfo.getDefaultSecurityParams().getSecurityType() 1901 == WifiConfiguration.SECURITY_TYPE_OPEN) { 1902 continue; 1903 } 1904 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = 1905 getNetworkSuggestionsForScanResultMatchInfo( 1906 scanResultMatchInfo, MacAddress.fromString(scanResult.BSSID)); 1907 if (extNetworkSuggestions == null || extNetworkSuggestions.isEmpty()) { 1908 continue; 1909 } 1910 Set<ExtendedWifiNetworkSuggestion> sharedNetworkSuggestions = extNetworkSuggestions 1911 .stream() 1912 .filter(ewns -> ewns.perAppInfo.hasUserApproved 1913 && ewns.wns.isUserAllowedToManuallyConnect) 1914 .collect(Collectors.toSet()); 1915 if (sharedNetworkSuggestions.isEmpty()) { 1916 continue; 1917 } 1918 for (ExtendedWifiNetworkSuggestion ewns : sharedNetworkSuggestions) { 1919 if (mVerboseLoggingEnabled) { 1920 Log.v(TAG, "getWifiConfigForMatchedNetworkSuggestionsSharedWithUser Found " 1921 + ewns + " for " + scanResult.SSID 1922 + "[" + scanResult.capabilities + "]"); 1923 } 1924 WifiConfiguration config = ewns.createInternalWifiConfiguration( 1925 mWifiCarrierInfoManager); 1926 if (config.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID 1927 && !mWifiCarrierInfoManager.isSimReady(config.subscriptionId)) { 1928 continue; 1929 } 1930 if (config.carrierMerged && !areCarrierMergedSuggestionsAllowed( 1931 config, ewns.perAppInfo.packageName)) { 1932 continue; 1933 } 1934 WifiConfiguration wCmWifiConfig = wifiConfigMap.get(config.getProfileKey()); 1935 if (wCmWifiConfig == null) { 1936 continue; 1937 } 1938 if (networkKeys.add(wCmWifiConfig.getProfileKey())) { 1939 sharedWifiConfigs.add(wCmWifiConfig); 1940 } 1941 } 1942 } 1943 return sharedWifiConfigs; 1944 } 1945 1946 /** 1947 * Check if the given passpoint suggestion has user approval and allow user manually connect. 1948 */ isPasspointSuggestionSharedWithUser(WifiConfiguration config)1949 public boolean isPasspointSuggestionSharedWithUser(WifiConfiguration config) { 1950 if (WifiConfiguration.isMetered(config, null) 1951 && mWifiCarrierInfoManager.isCarrierNetworkFromNonDefaultDataSim(config)) { 1952 return false; 1953 } 1954 if (config.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) { 1955 int subId = mWifiCarrierInfoManager.getBestMatchSubscriptionId(config); 1956 if (!mWifiCarrierInfoManager.isSimReady(subId)) { 1957 return false; 1958 } 1959 } 1960 Set<ExtendedWifiNetworkSuggestion> extendedWifiNetworkSuggestions = 1961 getNetworkSuggestionsForFqdnMatch(config.FQDN); 1962 Set<ExtendedWifiNetworkSuggestion> matchedSuggestions = 1963 extendedWifiNetworkSuggestions == null ? null : extendedWifiNetworkSuggestions 1964 .stream().filter(ewns -> ewns.perAppInfo.uid == config.creatorUid) 1965 .collect(Collectors.toSet()); 1966 if (matchedSuggestions == null || matchedSuggestions.isEmpty()) { 1967 Log.e(TAG, "Matched network suggestion is missing for FQDN:" + config.FQDN); 1968 return false; 1969 } 1970 ExtendedWifiNetworkSuggestion suggestion = matchedSuggestions 1971 .stream().findAny().get(); 1972 return suggestion.wns.isUserAllowedToManuallyConnect 1973 && suggestion.perAppInfo.hasUserApproved; 1974 } 1975 1976 /** 1977 * Get hidden network from active network suggestions. 1978 * Todo(): Now limit by a fixed number, maybe we can try rotation? 1979 * @param autoJoinOnly retrieve hidden network autojoin enabled only. 1980 * @return list of HiddenNetwork 1981 */ retrieveHiddenNetworkList( boolean autoJoinOnly)1982 public List<WifiScanner.ScanSettings.HiddenNetwork> retrieveHiddenNetworkList( 1983 boolean autoJoinOnly) { 1984 List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworks = new ArrayList<>(); 1985 for (PerAppInfo appInfo : mActiveNetworkSuggestionsPerApp.values()) { 1986 if (!appInfo.hasUserApproved) continue; 1987 for (ExtendedWifiNetworkSuggestion ewns : appInfo.extNetworkSuggestions.values()) { 1988 if (!ewns.wns.wifiConfiguration.hiddenSSID) continue; 1989 if (autoJoinOnly && !ewns.isAutojoinEnabled) continue; 1990 hiddenNetworks.add( 1991 new WifiScanner.ScanSettings.HiddenNetwork( 1992 ewns.wns.wifiConfiguration.SSID)); 1993 if (hiddenNetworks.size() >= NUMBER_OF_HIDDEN_NETWORK_FOR_ONE_SCAN) { 1994 return hiddenNetworks; 1995 } 1996 } 1997 } 1998 return hiddenNetworks; 1999 } 2000 2001 /** 2002 * Helper method to send the post connection broadcast to specified package. 2003 */ sendPostConnectionBroadcast( ExtendedWifiNetworkSuggestion extSuggestion)2004 private void sendPostConnectionBroadcast( 2005 ExtendedWifiNetworkSuggestion extSuggestion) { 2006 Intent intent = new Intent(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION); 2007 intent.putExtra(WifiManager.EXTRA_NETWORK_SUGGESTION, extSuggestion.wns); 2008 // Intended to wakeup the receiving app so set the specific package name. 2009 intent.setPackage(extSuggestion.perAppInfo.packageName); 2010 mContext.sendBroadcastAsUser( 2011 intent, UserHandle.getUserHandleForUid(extSuggestion.perAppInfo.uid)); 2012 } 2013 2014 /** 2015 * Helper method to send the post connection broadcast to specified package. 2016 */ sendPostConnectionBroadcastIfAllowed( ExtendedWifiNetworkSuggestion matchingExtSuggestion, @NonNull String message)2017 private void sendPostConnectionBroadcastIfAllowed( 2018 ExtendedWifiNetworkSuggestion matchingExtSuggestion, @NonNull String message) { 2019 try { 2020 mWifiPermissionsUtil.enforceCanAccessScanResults( 2021 matchingExtSuggestion.perAppInfo.packageName, 2022 matchingExtSuggestion.perAppInfo.featureId, 2023 matchingExtSuggestion.perAppInfo.uid, message); 2024 } catch (SecurityException se) { 2025 Log.w(TAG, "Permission denied for sending post connection broadcast to " 2026 + matchingExtSuggestion.perAppInfo.packageName); 2027 return; 2028 } 2029 if (mVerboseLoggingEnabled) { 2030 Log.v(TAG, "Sending post connection broadcast to " 2031 + matchingExtSuggestion.perAppInfo.packageName); 2032 } 2033 sendPostConnectionBroadcast(matchingExtSuggestion); 2034 } 2035 2036 /** 2037 * Send out the {@link WifiManager#ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} to the 2038 * network suggestion that provided credential for the current connection network. 2039 * If current connection network is open user saved network, broadcast will be only sent out to 2040 * one of the carrier apps that suggested matched network suggestions. 2041 * 2042 * @param connectedNetwork {@link WifiConfiguration} representing the network connected to. 2043 * @param connectedBssid BSSID of the network connected to. 2044 */ handleConnectionSuccess( @onNull WifiConfiguration connectedNetwork, @NonNull String connectedBssid)2045 private void handleConnectionSuccess( 2046 @NonNull WifiConfiguration connectedNetwork, @NonNull String connectedBssid) { 2047 if (!(connectedNetwork.fromWifiNetworkSuggestion || connectedNetwork.isOpenNetwork())) { 2048 return; 2049 } 2050 Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions = 2051 getNetworkSuggestionsForWifiConfiguration(connectedNetwork, connectedBssid); 2052 2053 if (mVerboseLoggingEnabled) { 2054 Log.v(TAG, "Network suggestions matching the connection " 2055 + matchingExtNetworkSuggestions); 2056 } 2057 if (matchingExtNetworkSuggestions == null 2058 || matchingExtNetworkSuggestions.isEmpty()) return; 2059 2060 Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsFromTargetApp; 2061 if (connectedNetwork.fromWifiNetworkSuggestion) { 2062 matchingExtNetworkSuggestionsFromTargetApp = 2063 getMatchedSuggestionsWithSameProfileKey(matchingExtNetworkSuggestions, 2064 connectedNetwork); 2065 if (matchingExtNetworkSuggestionsFromTargetApp.isEmpty()) { 2066 Log.wtf(TAG, "Current connected network suggestion is missing!"); 2067 return; 2068 } 2069 } else { 2070 // If not suggestion, the connected network is open network. 2071 // For saved open network, found the matching suggestion from carrier privileged 2072 // apps. As we only expect one suggestor app to take action on post connection, if 2073 // multiple apps suggested matched suggestions, framework will randomly pick one. 2074 matchingExtNetworkSuggestionsFromTargetApp = matchingExtNetworkSuggestions.stream() 2075 .filter(x -> x.perAppInfo.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID 2076 || mWifiPermissionsUtil 2077 .checkNetworkCarrierProvisioningPermission(x.perAppInfo.uid)) 2078 .limit(1).collect(Collectors.toSet()); 2079 if (matchingExtNetworkSuggestionsFromTargetApp.isEmpty()) { 2080 if (mVerboseLoggingEnabled) { 2081 Log.v(TAG, "No suggestion matched connected user saved open network."); 2082 } 2083 return; 2084 } 2085 } 2086 2087 mWifiMetrics.incrementNetworkSuggestionApiNumConnectSuccess(); 2088 // Find subset of network suggestions have set |isAppInteractionRequired|. 2089 Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsWithReqAppInteraction = 2090 matchingExtNetworkSuggestionsFromTargetApp.stream() 2091 .filter(x -> x.wns.isAppInteractionRequired) 2092 .collect(Collectors.toSet()); 2093 if (matchingExtNetworkSuggestionsWithReqAppInteraction.isEmpty()) return; 2094 2095 // Iterate over the matching network suggestions list: 2096 // a) Ensure that these apps have the necessary location permissions. 2097 // b) Send directed broadcast to the app with their corresponding network suggestion. 2098 for (ExtendedWifiNetworkSuggestion matchingExtNetworkSuggestion 2099 : matchingExtNetworkSuggestionsWithReqAppInteraction) { 2100 sendPostConnectionBroadcastIfAllowed( 2101 matchingExtNetworkSuggestion, 2102 "Connected to " + matchingExtNetworkSuggestion.wns.wifiConfiguration.SSID 2103 + ". featureId is first feature of the app using network suggestions"); 2104 } 2105 } 2106 2107 /** 2108 * Handle connection failure. 2109 * 2110 * @param network {@link WifiConfiguration} representing the network that connection failed to. 2111 * @param bssid BSSID of the network connection failed to if known, else null. 2112 * @param failureCode failure reason code. 2113 */ handleConnectionFailure(@onNull WifiConfiguration network, @Nullable String bssid, int failureCode)2114 private void handleConnectionFailure(@NonNull WifiConfiguration network, 2115 @Nullable String bssid, int failureCode) { 2116 if (!network.fromWifiNetworkSuggestion) { 2117 return; 2118 } 2119 Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions = 2120 getNetworkSuggestionsForWifiConfiguration(network, bssid); 2121 if (mVerboseLoggingEnabled) { 2122 Log.v(TAG, "Network suggestions matching the connection failure " 2123 + matchingExtNetworkSuggestions); 2124 } 2125 if (matchingExtNetworkSuggestions == null 2126 || matchingExtNetworkSuggestions.isEmpty()) return; 2127 2128 mWifiMetrics.incrementNetworkSuggestionApiNumConnectFailure(); 2129 // TODO (b/115504887, b/112196799): Blocklist the corresponding network suggestion if 2130 // the connection failed. 2131 2132 // Find subset of network suggestions which suggested the connection failure network. 2133 Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsFromTargetApp = 2134 getMatchedSuggestionsWithSameProfileKey(matchingExtNetworkSuggestions, network); 2135 if (matchingExtNetworkSuggestionsFromTargetApp.isEmpty()) { 2136 Log.wtf(TAG, "Current connection failure network suggestion is missing!"); 2137 return; 2138 } 2139 2140 for (ExtendedWifiNetworkSuggestion matchingExtNetworkSuggestion 2141 : matchingExtNetworkSuggestionsFromTargetApp) { 2142 sendConnectionFailureIfAllowed(matchingExtNetworkSuggestion.perAppInfo.packageName, 2143 matchingExtNetworkSuggestion.perAppInfo.featureId, 2144 matchingExtNetworkSuggestion.perAppInfo.uid, 2145 matchingExtNetworkSuggestion.wns, failureCode); 2146 } 2147 } 2148 getMatchedSuggestionsWithSameProfileKey( Set<ExtendedWifiNetworkSuggestion> matchingSuggestions, WifiConfiguration network)2149 private Set<ExtendedWifiNetworkSuggestion> getMatchedSuggestionsWithSameProfileKey( 2150 Set<ExtendedWifiNetworkSuggestion> matchingSuggestions, WifiConfiguration network) { 2151 if (matchingSuggestions == null || matchingSuggestions.isEmpty()) { 2152 return Set.of(); 2153 } 2154 Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsWithSameProfileKey = 2155 new HashSet<>(); 2156 for (ExtendedWifiNetworkSuggestion ewns : matchingSuggestions) { 2157 WifiConfiguration config = ewns 2158 .createInternalWifiConfiguration(mWifiCarrierInfoManager); 2159 if (config.getProfileKey().equals(network.getProfileKey()) 2160 && config.creatorName.equals(network.creatorName)) { 2161 matchingExtNetworkSuggestionsWithSameProfileKey.add(ewns); 2162 } 2163 } 2164 return matchingExtNetworkSuggestionsWithSameProfileKey; 2165 } 2166 2167 /** 2168 * Invoked by {@link ClientModeImpl} on end of connection attempt to a network. 2169 * 2170 * @param failureCode Failure codes representing {@link WifiMetrics.ConnectionEvent} codes. 2171 * @param network WifiConfiguration corresponding to the current network. 2172 * @param bssid BSSID of the current network. 2173 */ handleConnectionAttemptEnded( int failureCode, @NonNull WifiConfiguration network, @Nullable String bssid)2174 public void handleConnectionAttemptEnded( 2175 int failureCode, @NonNull WifiConfiguration network, @Nullable String bssid) { 2176 if (mVerboseLoggingEnabled) { 2177 Log.v(TAG, "handleConnectionAttemptEnded " + failureCode + ", " + network); 2178 } 2179 if (failureCode == WifiMetrics.ConnectionEvent.FAILURE_NONE) { 2180 handleConnectionSuccess(network, bssid); 2181 } else { 2182 handleConnectionFailure(network, bssid, failureCode); 2183 } 2184 } 2185 2186 /** 2187 * Send network connection failure event to app when an connection attempt failure. 2188 * @param packageName package name to send event 2189 * @param featureId The feature in the package 2190 * @param uid uid of the app. 2191 * @param matchingSuggestion suggestion on this connection failure 2192 * @param connectionEvent connection failure code 2193 */ sendConnectionFailureIfAllowed(String packageName, @Nullable String featureId, int uid, @NonNull WifiNetworkSuggestion matchingSuggestion, int connectionEvent)2194 private void sendConnectionFailureIfAllowed(String packageName, @Nullable String featureId, 2195 int uid, @NonNull WifiNetworkSuggestion matchingSuggestion, int connectionEvent) { 2196 RemoteCallbackList<ISuggestionConnectionStatusListener> listenersTracker = 2197 mSuggestionStatusListenerPerApp.get(packageName); 2198 if (listenersTracker == null || listenersTracker.getRegisteredCallbackCount() == 0) { 2199 return; 2200 } 2201 try { 2202 mWifiPermissionsUtil.enforceCanAccessScanResults( 2203 packageName, featureId, uid, "Connection failure"); 2204 } catch (SecurityException se) { 2205 Log.w(TAG, "Permission denied for sending connection failure event to " + packageName); 2206 return; 2207 } 2208 if (mVerboseLoggingEnabled) { 2209 Log.v(TAG, "Sending connection failure event to " + packageName); 2210 } 2211 int itemCount = listenersTracker.beginBroadcast(); 2212 for (int i = 0; i < itemCount; i++) { 2213 try { 2214 listenersTracker.getBroadcastItem(i).onConnectionStatus(matchingSuggestion, 2215 internalConnectionEventToSuggestionFailureCode(connectionEvent)); 2216 } catch (RemoteException e) { 2217 Log.e(TAG, "sendNetworkCallback: remote exception -- " + e); 2218 } 2219 } 2220 listenersTracker.finishBroadcast(); 2221 } 2222 2223 private @WifiManager.SuggestionConnectionStatusCode internalConnectionEventToSuggestionFailureCode(int connectionEvent)2224 int internalConnectionEventToSuggestionFailureCode(int connectionEvent) { 2225 switch (connectionEvent) { 2226 case WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION: 2227 case WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT: 2228 return WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_ASSOCIATION; 2229 case WifiMetrics.ConnectionEvent.FAILURE_SSID_TEMP_DISABLED: 2230 case WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE: 2231 return WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_AUTHENTICATION; 2232 case WifiMetrics.ConnectionEvent.FAILURE_DHCP: 2233 return WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_IP_PROVISIONING; 2234 default: 2235 return WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_UNKNOWN; 2236 } 2237 } 2238 2239 /** 2240 * Register a SuggestionUserApprovalStatusListener on user approval status changes. 2241 * @param listener ISuggestionUserApprovalStatusListener instance to add. 2242 * @param uid uid of the app. 2243 */ addSuggestionUserApprovalStatusListener( @onNull ISuggestionUserApprovalStatusListener listener, String packageName, int uid)2244 public void addSuggestionUserApprovalStatusListener( 2245 @NonNull ISuggestionUserApprovalStatusListener listener, String packageName, int uid) { 2246 RemoteCallbackList<ISuggestionUserApprovalStatusListener> listenersTracker = 2247 mSuggestionUserApprovalStatusListenerPerApp.get(packageName); 2248 if (listenersTracker == null) { 2249 listenersTracker = new RemoteCallbackList<>(); 2250 } 2251 listenersTracker.register(listener); 2252 mSuggestionUserApprovalStatusListenerPerApp.put(packageName, listenersTracker); 2253 try { 2254 listener.onUserApprovalStatusChange( 2255 getNetworkSuggestionUserApprovalStatus(uid, packageName)); 2256 } catch (RemoteException e) { 2257 Log.e(TAG, "sendUserApprovalStatusChange: remote exception -- " + e); 2258 } 2259 } 2260 2261 /** 2262 * Unregister a listener on on user approval status changes. 2263 * @param listener ISuggestionUserApprovalStatusListener instance to remove. 2264 * @param uid uid of the app. 2265 */ removeSuggestionUserApprovalStatusListener( @onNull ISuggestionUserApprovalStatusListener listener, String packageName, int uid)2266 public void removeSuggestionUserApprovalStatusListener( 2267 @NonNull ISuggestionUserApprovalStatusListener listener, String packageName, int uid) { 2268 RemoteCallbackList<ISuggestionUserApprovalStatusListener> listenersTracker = 2269 mSuggestionUserApprovalStatusListenerPerApp.get(packageName); 2270 if (listenersTracker == null || !listenersTracker.unregister(listener)) { 2271 Log.w(TAG, "removeSuggestionUserApprovalStatusListener: Listener from " + packageName 2272 + " already removed."); 2273 return; 2274 } 2275 if (listenersTracker != null && listenersTracker.getRegisteredCallbackCount() == 0) { 2276 mSuggestionUserApprovalStatusListenerPerApp.remove(packageName); 2277 } 2278 } 2279 2280 /** 2281 * Register a SuggestionConnectionStatusListener on network connection failure. 2282 * @param listener ISuggestionNetworkCallback instance to add. 2283 * @param uid uid of the app. 2284 * @return true if succeed otherwise false. 2285 */ registerSuggestionConnectionStatusListener( @onNull ISuggestionConnectionStatusListener listener, String packageName, int uid)2286 public boolean registerSuggestionConnectionStatusListener( 2287 @NonNull ISuggestionConnectionStatusListener listener, 2288 String packageName, int uid) { 2289 if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(uid)) { 2290 Log.e(TAG, "UID " + uid + " not visible to the current user"); 2291 return false; 2292 } 2293 RemoteCallbackList<ISuggestionConnectionStatusListener> listenersTracker = 2294 mSuggestionStatusListenerPerApp.get(packageName); 2295 if (listenersTracker == null) { 2296 listenersTracker = new RemoteCallbackList<>(); 2297 } 2298 listenersTracker.register(listener); 2299 mSuggestionStatusListenerPerApp.put(packageName, listenersTracker); 2300 return true; 2301 } 2302 2303 /** 2304 * Unregister a listener on network connection failure. 2305 * @param listener ISuggestionNetworkCallback instance to remove. 2306 * @param uid uid of the app. 2307 */ unregisterSuggestionConnectionStatusListener( @onNull ISuggestionConnectionStatusListener listener, String packageName, int uid)2308 public void unregisterSuggestionConnectionStatusListener( 2309 @NonNull ISuggestionConnectionStatusListener listener, String packageName, int uid) { 2310 if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(uid)) { 2311 Log.e(TAG, "UID " + uid + " not visible to the current user"); 2312 return; 2313 } 2314 RemoteCallbackList<ISuggestionConnectionStatusListener> listenersTracker = 2315 mSuggestionStatusListenerPerApp.get(packageName); 2316 if (listenersTracker == null || !listenersTracker.unregister(listener)) { 2317 Log.w(TAG, "unregisterSuggestionConnectionStatusListener: Listener from " + packageName 2318 + " already unregister."); 2319 } 2320 if (listenersTracker != null && listenersTracker.getRegisteredCallbackCount() == 0) { 2321 mSuggestionStatusListenerPerApp.remove(packageName); 2322 } 2323 } 2324 2325 /** 2326 * When SIM state changes, check if carrier privileges changes for app. 2327 * If app changes from privileged to not privileged, remove all suggestions and reset state. 2328 * If app changes from not privileges to privileged, set target carrier id for all suggestions. 2329 */ updateCarrierPrivilegedApps()2330 public void updateCarrierPrivilegedApps() { 2331 if (SdkLevel.isAtLeastT()) { 2332 return; 2333 } 2334 Log.w(TAG, "SIM state is changed!"); 2335 Iterator<Map.Entry<String, PerAppInfo>> iter = 2336 mActiveNetworkSuggestionsPerApp.entrySet().iterator(); 2337 while (iter.hasNext()) { 2338 PerAppInfo appInfo = iter.next().getValue(); 2339 int carrierId = mWifiCarrierInfoManager 2340 .getCarrierIdForPackageWithCarrierPrivileges(appInfo.packageName); 2341 if (carrierId == appInfo.carrierId) { 2342 continue; 2343 } 2344 if (carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) { 2345 Log.i(TAG, "Carrier privilege revoked for " + appInfo.packageName); 2346 removeInternal(List.of(), appInfo.packageName, appInfo, 2347 ACTION_REMOVE_SUGGESTION_DISCONNECT); 2348 iter.remove(); 2349 continue; 2350 } 2351 Log.i(TAG, "Carrier privilege granted for " + appInfo.packageName); 2352 appInfo.carrierId = carrierId; 2353 for (ExtendedWifiNetworkSuggestion ewns : appInfo.extNetworkSuggestions.values()) { 2354 ewns.wns.wifiConfiguration.carrierId = carrierId; 2355 } 2356 } 2357 saveToStore(); 2358 } 2359 2360 /** 2361 * When carrier privileged packages list changes, handle the apps which privileged state changed 2362 * - If app changes from privileged to not privileged, remove all suggestions and reset state 2363 * - If app changes from not privileges to privileged, set target carrier id for all suggestions 2364 */ updateCarrierPrivilegedApps(Set<String> privilegedApps)2365 public void updateCarrierPrivilegedApps(Set<String> privilegedApps) { 2366 if (!SdkLevel.isAtLeastT()) { 2367 return; 2368 } 2369 if (mVerboseLoggingEnabled) { 2370 StringBuilder stringBuilder = new StringBuilder(); 2371 stringBuilder.append("Carrier privileged packages changed, privileged apps=["); 2372 for (String packagesName : privilegedApps) { 2373 stringBuilder.append(packagesName).append(", "); 2374 } 2375 stringBuilder.append("]"); 2376 Log.d(TAG, stringBuilder.toString()); 2377 } 2378 Iterator<Map.Entry<String, PerAppInfo>> iter = 2379 mActiveNetworkSuggestionsPerApp.entrySet().iterator(); 2380 while (iter.hasNext()) { 2381 PerAppInfo appInfo = iter.next().getValue(); 2382 if (privilegedApps.contains(appInfo.packageName)) { 2383 if (appInfo.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) { 2384 // Already privileged before, no change. 2385 continue; 2386 } 2387 // for (newly) privileged packages: update carrier ID 2388 int carrierId = mWifiCarrierInfoManager 2389 .getCarrierIdForPackageWithCarrierPrivileges(appInfo.packageName); 2390 Log.i(TAG, "Carrier privilege granted for " + appInfo.packageName); 2391 appInfo.carrierId = carrierId; 2392 for (ExtendedWifiNetworkSuggestion ewns : appInfo.extNetworkSuggestions.values()) { 2393 ewns.wns.wifiConfiguration.carrierId = carrierId; 2394 } 2395 continue; 2396 } 2397 if (appInfo.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) { 2398 // Apps never got privileged, no change. 2399 continue; 2400 } 2401 // Carrier privilege revoked, remove. 2402 Log.i(TAG, "Carrier privilege revoked for " + appInfo.packageName); 2403 removeInternal(List.of(), appInfo.packageName, appInfo, 2404 ACTION_REMOVE_SUGGESTION_DISCONNECT); 2405 iter.remove(); 2406 } 2407 saveToStore(); 2408 } 2409 2410 /** 2411 * Resets all sim networks state. 2412 */ resetSimNetworkSuggestions()2413 public void resetSimNetworkSuggestions() { 2414 mActiveNetworkSuggestionsPerApp.values().stream() 2415 .flatMap(e -> e.extNetworkSuggestions.values().stream()) 2416 .forEach(ewns -> ewns.anonymousIdentity = null); 2417 saveToStore(); 2418 } 2419 2420 /** 2421 * Set auto-join enable/disable for suggestion network 2422 * @param config WifiConfiguration which is to change. 2423 * @param choice true to enable auto-join, false to disable. 2424 * @return true on success, false otherwise (e.g. if no match suggestion exists). 2425 */ allowNetworkSuggestionAutojoin(WifiConfiguration config, boolean choice)2426 public boolean allowNetworkSuggestionAutojoin(WifiConfiguration config, boolean choice) { 2427 if (!config.fromWifiNetworkSuggestion) { 2428 Log.e(TAG, "allowNetworkSuggestionAutojoin: on non-suggestion network: " 2429 + config); 2430 return false; 2431 } 2432 2433 if (config.isPasspoint()) { 2434 if (!mWifiInjector.getPasspointManager().enableAutojoin(config.getProfileKey(), 2435 null, choice)) { 2436 return false; 2437 } 2438 } 2439 2440 Set<ExtendedWifiNetworkSuggestion> matchingExtendedWifiNetworkSuggestions = 2441 getMatchedSuggestionsWithSameProfileKey( 2442 getNetworkSuggestionsForWifiConfiguration(config, config.BSSID), config); 2443 if (matchingExtendedWifiNetworkSuggestions.isEmpty()) { 2444 Log.e(TAG, "allowNetworkSuggestionAutojoin: network is missing: " 2445 + config); 2446 return false; 2447 } 2448 for (ExtendedWifiNetworkSuggestion ewns : matchingExtendedWifiNetworkSuggestions) { 2449 ewns.isAutojoinEnabled = choice; 2450 } 2451 saveToStore(); 2452 return true; 2453 } 2454 2455 /** 2456 * Get the filtered ScanResults which may be authenticated by the suggested configurations. 2457 * @param wifiNetworkSuggestions The list of {@link WifiNetworkSuggestion} 2458 * @param scanResults The list of {@link ScanResult} 2459 * @return The filtered ScanResults 2460 */ 2461 @NonNull getMatchingScanResults( @onNull List<WifiNetworkSuggestion> wifiNetworkSuggestions, @NonNull List<ScanResult> scanResults)2462 public Map<WifiNetworkSuggestion, List<ScanResult>> getMatchingScanResults( 2463 @NonNull List<WifiNetworkSuggestion> wifiNetworkSuggestions, 2464 @NonNull List<ScanResult> scanResults) { 2465 Map<WifiNetworkSuggestion, List<ScanResult>> filteredScanResults = new HashMap<>(); 2466 if (wifiNetworkSuggestions == null || wifiNetworkSuggestions.isEmpty() 2467 || scanResults == null || scanResults.isEmpty()) { 2468 return filteredScanResults; 2469 } 2470 for (WifiNetworkSuggestion suggestion : wifiNetworkSuggestions) { 2471 if (suggestion == null || suggestion.wifiConfiguration == null) { 2472 continue; 2473 } 2474 filteredScanResults.put(suggestion, 2475 getMatchingScanResultsForSuggestion(suggestion, scanResults)); 2476 } 2477 2478 return filteredScanResults; 2479 } 2480 getMatchingScanResultsForSuggestion(WifiNetworkSuggestion suggestion, List<ScanResult> scanResults)2481 private List<ScanResult> getMatchingScanResultsForSuggestion(WifiNetworkSuggestion suggestion, 2482 List<ScanResult> scanResults) { 2483 if (suggestion.passpointConfiguration != null) { 2484 return mWifiInjector.getPasspointManager().getMatchingScanResults( 2485 suggestion.passpointConfiguration, scanResults); 2486 } else { 2487 return getMatchingScanResults(suggestion.wifiConfiguration, scanResults); 2488 } 2489 } 2490 2491 /** 2492 * Get the filtered ScanResults which may be authenticated by the {@link WifiConfiguration}. 2493 * @param wifiConfiguration The instance of {@link WifiConfiguration} 2494 * @param scanResults The list of {@link ScanResult} 2495 * @return The filtered ScanResults 2496 */ 2497 @NonNull getMatchingScanResults( @onNull WifiConfiguration wifiConfiguration, @NonNull List<ScanResult> scanResults)2498 private List<ScanResult> getMatchingScanResults( 2499 @NonNull WifiConfiguration wifiConfiguration, 2500 @NonNull List<ScanResult> scanResults) { 2501 ScanResultMatchInfo matchInfoFromConfigration = 2502 ScanResultMatchInfo.fromWifiConfiguration(wifiConfiguration); 2503 if (matchInfoFromConfigration == null) { 2504 return new ArrayList<>(); 2505 } 2506 List<ScanResult> filteredScanResult = new ArrayList<>(); 2507 for (ScanResult scanResult : scanResults) { 2508 if (matchInfoFromConfigration.equals(ScanResultMatchInfo.fromScanResult(scanResult))) { 2509 filteredScanResult.add(scanResult); 2510 } 2511 } 2512 2513 return filteredScanResult; 2514 } 2515 2516 /** 2517 * Add the suggestion update event listener 2518 */ addOnSuggestionUpdateListener(OnSuggestionUpdateListener listener)2519 public void addOnSuggestionUpdateListener(OnSuggestionUpdateListener listener) { 2520 mListeners.add(listener); 2521 } 2522 2523 /** 2524 * When a saved open network has a same network suggestion which is from app has 2525 * NETWORK_CARRIER_PROVISIONING permission, also that app suggested secure network suggestion 2526 * for same carrier with higher or equal priority and Auto-Join enabled, also that secure 2527 * network is in the range. The saved open network will be ignored during the network selection. 2528 * TODO (b/142035508): revert all these changes once we build infra needed to solve this. 2529 * @param configuration Saved open network to check if it should be ignored. 2530 * @param scanDetails Available ScanDetail nearby. 2531 * @return True if the open network should be ignored, false otherwise. 2532 */ shouldBeIgnoredBySecureSuggestionFromSameCarrier( @onNull WifiConfiguration configuration, List<ScanDetail> scanDetails)2533 public boolean shouldBeIgnoredBySecureSuggestionFromSameCarrier( 2534 @NonNull WifiConfiguration configuration, List<ScanDetail> scanDetails) { 2535 if (!mResources.getBoolean( 2536 R.bool.config_wifiIgnoreOpenSavedNetworkWhenSecureSuggestionAvailable)) { 2537 return false; 2538 } 2539 if (configuration == null || scanDetails == null || !configuration.isOpenNetwork()) { 2540 return false; 2541 } 2542 Set<ExtendedWifiNetworkSuggestion> matchedExtSuggestions = 2543 getNetworkSuggestionsForWifiConfiguration(configuration, null); 2544 if (matchedExtSuggestions == null || matchedExtSuggestions.isEmpty()) { 2545 return false; 2546 } 2547 matchedExtSuggestions = matchedExtSuggestions.stream().filter(ewns -> 2548 mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(ewns.perAppInfo.uid)) 2549 .collect(Collectors.toSet()); 2550 if (matchedExtSuggestions.isEmpty()) { 2551 return false; 2552 } 2553 for (ExtendedWifiNetworkSuggestion ewns : matchedExtSuggestions) { 2554 if (hasSecureSuggestionFromSameCarrierAvailable(ewns, scanDetails)) { 2555 return true; 2556 } 2557 } 2558 return false; 2559 } 2560 2561 /** 2562 * Check the suggestion user approval status. 2563 */ getNetworkSuggestionUserApprovalStatus( int uid, String packageName)2564 private @WifiManager.SuggestionUserApprovalStatus int getNetworkSuggestionUserApprovalStatus( 2565 int uid, String packageName) { 2566 if (mAppOps.unsafeCheckOpNoThrow(OPSTR_CHANGE_WIFI_STATE, uid, packageName) 2567 == AppOpsManager.MODE_IGNORED) { 2568 return WifiManager.STATUS_SUGGESTION_APPROVAL_REJECTED_BY_USER; 2569 } 2570 if (!mActiveNetworkSuggestionsPerApp.containsKey(packageName)) { 2571 return WifiManager.STATUS_SUGGESTION_APPROVAL_UNKNOWN; 2572 } 2573 PerAppInfo info = mActiveNetworkSuggestionsPerApp.get(packageName); 2574 if (info.hasUserApproved) { 2575 return WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER; 2576 } 2577 if (info.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) { 2578 return WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_CARRIER_PRIVILEGE; 2579 } 2580 return WifiManager.STATUS_SUGGESTION_APPROVAL_PENDING; 2581 } 2582 hasSecureSuggestionFromSameCarrierAvailable( ExtendedWifiNetworkSuggestion extendedWifiNetworkSuggestion, List<ScanDetail> scanDetails)2583 private boolean hasSecureSuggestionFromSameCarrierAvailable( 2584 ExtendedWifiNetworkSuggestion extendedWifiNetworkSuggestion, 2585 List<ScanDetail> scanDetails) { 2586 boolean isOpenSuggestionMetered = WifiConfiguration.isMetered( 2587 extendedWifiNetworkSuggestion.wns.wifiConfiguration, null); 2588 Set<ExtendedWifiNetworkSuggestion> secureExtSuggestions = new HashSet<>(); 2589 for (ExtendedWifiNetworkSuggestion ewns : extendedWifiNetworkSuggestion.perAppInfo 2590 .extNetworkSuggestions.values()) { 2591 // Open network and auto-join disable suggestion, ignore. 2592 if (isOpenSuggestion(ewns) || !ewns.isAutojoinEnabled) { 2593 continue; 2594 } 2595 // From different carrier as open suggestion, ignore. 2596 if (getCarrierIdFromSuggestion(ewns) 2597 != getCarrierIdFromSuggestion(extendedWifiNetworkSuggestion)) { 2598 continue; 2599 } 2600 // Secure and open has different meterness, ignore 2601 if (WifiConfiguration.isMetered(ewns.wns.wifiConfiguration, null) 2602 != isOpenSuggestionMetered) { 2603 continue; 2604 } 2605 // Low priority than open suggestion, ignore. 2606 if (ewns.wns.wifiConfiguration.priority 2607 < extendedWifiNetworkSuggestion.wns.wifiConfiguration.priority) { 2608 continue; 2609 } 2610 WifiConfiguration wcmConfig = mWifiConfigManager 2611 .getConfiguredNetwork(ewns.wns.wifiConfiguration.getProfileKey()); 2612 // Network selection is disabled, ignore. 2613 if (wcmConfig != null && !wcmConfig.getNetworkSelectionStatus().isNetworkEnabled()) { 2614 continue; 2615 } 2616 secureExtSuggestions.add(ewns); 2617 } 2618 2619 if (secureExtSuggestions.isEmpty()) { 2620 return false; 2621 } 2622 List<ScanResult> scanResults = scanDetails.stream().map(ScanDetail::getScanResult) 2623 .collect(Collectors.toList()); 2624 // Check if the secure suggestion is in the range. 2625 for (ExtendedWifiNetworkSuggestion ewns : secureExtSuggestions) { 2626 if (!getMatchingScanResultsForSuggestion(ewns.wns, scanResults).isEmpty()) { 2627 return true; 2628 } 2629 } 2630 return false; 2631 } 2632 2633 /** 2634 * Set the app treated as cross carrier provider. That can suggest for any carrier 2635 * @param packageName App name to set. 2636 * @param enabled True to set app treated as cross carrier provider, false otherwise. 2637 */ setAppWorkingAsCrossCarrierProvider(String packageName, boolean enabled)2638 public void setAppWorkingAsCrossCarrierProvider(String packageName, boolean enabled) { 2639 if (enabled) { 2640 mCrossCarrierProvidersSet.add(packageName); 2641 } else { 2642 mCrossCarrierProvidersSet.remove(packageName); 2643 } 2644 } 2645 2646 /** 2647 * Check whether the app is treated as a cross carrier provider or not. 2648 * @param packageName App name to check 2649 * @return True for app is treated as a carrier provider, false otherwise. 2650 */ isAppWorkingAsCrossCarrierProvider(String packageName)2651 public boolean isAppWorkingAsCrossCarrierProvider(String packageName) { 2652 return mCrossCarrierProvidersSet.contains(packageName); 2653 } 2654 2655 /** 2656 * Store Anonymous Identity for SIM based suggestion after connection. 2657 */ setAnonymousIdentity(WifiConfiguration config)2658 public void setAnonymousIdentity(WifiConfiguration config) { 2659 if (config.isPasspoint() || !config.fromWifiNetworkSuggestion) { 2660 return; 2661 } 2662 if (config.enterpriseConfig == null 2663 || !config.enterpriseConfig.isAuthenticationSimBased()) { 2664 Log.e(TAG, "Network is not SIM based, AnonymousIdentity is invalid"); 2665 } 2666 Set<ExtendedWifiNetworkSuggestion> matchedSuggestionSet = 2667 getMatchedSuggestionsWithSameProfileKey( 2668 getNetworkSuggestionsForWifiConfiguration(config, config.BSSID), config); 2669 if (matchedSuggestionSet.isEmpty()) { 2670 Log.wtf(TAG, "Current connected SIM based network suggestion is missing!"); 2671 return; 2672 } 2673 for (ExtendedWifiNetworkSuggestion ewns : matchedSuggestionSet) { 2674 ewns.anonymousIdentity = config.enterpriseConfig.getAnonymousIdentity(); 2675 } 2676 saveToStore(); 2677 } 2678 isOpenSuggestion(ExtendedWifiNetworkSuggestion extendedWifiNetworkSuggestion)2679 private boolean isOpenSuggestion(ExtendedWifiNetworkSuggestion extendedWifiNetworkSuggestion) { 2680 if (extendedWifiNetworkSuggestion.wns.passpointConfiguration != null) { 2681 return false; 2682 } 2683 return extendedWifiNetworkSuggestion.wns.wifiConfiguration.isOpenNetwork(); 2684 } 2685 onUserConnectChoiceSetForSuggestion(Collection<WifiConfiguration> networks, String choiceKey, int rssi)2686 private void onUserConnectChoiceSetForSuggestion(Collection<WifiConfiguration> networks, 2687 String choiceKey, int rssi) { 2688 Set<String> networkKeys = networks.stream() 2689 .filter(config -> config.fromWifiNetworkSuggestion) 2690 .map(WifiConfiguration::getProfileKey) 2691 .collect(Collectors.toSet()); 2692 mActiveNetworkSuggestionsPerApp.values().stream() 2693 .flatMap(e -> e.extNetworkSuggestions.values().stream()) 2694 .forEach(ewns -> { 2695 String profileKey = ewns 2696 .createInternalWifiConfiguration(mWifiCarrierInfoManager) 2697 .getProfileKey(); 2698 if (TextUtils.equals(profileKey, choiceKey)) { 2699 ewns.connectChoice = null; 2700 ewns.connectChoiceRssi = 0; 2701 } else if (networkKeys.contains(profileKey)) { 2702 ewns.connectChoice = choiceKey; 2703 ewns.connectChoiceRssi = rssi; 2704 } 2705 }); 2706 saveToStore(); 2707 } 2708 onUserConnectChoiceRemoveForSuggestion(String choiceKey)2709 private void onUserConnectChoiceRemoveForSuggestion(String choiceKey) { 2710 mActiveNetworkSuggestionsPerApp.values().stream() 2711 .flatMap(e -> e.extNetworkSuggestions.values().stream()) 2712 .filter(ewns -> TextUtils.equals(ewns.connectChoice, choiceKey)) 2713 .forEach(ewns -> { 2714 ewns.connectChoice = null; 2715 ewns.connectChoiceRssi = 0; 2716 }); 2717 saveToStore(); 2718 } 2719 onSuggestionUserApprovalStatusChanged(int uid, String packageName)2720 private void onSuggestionUserApprovalStatusChanged(int uid, String packageName) { 2721 RemoteCallbackList<ISuggestionUserApprovalStatusListener> listenersTracker = 2722 mSuggestionUserApprovalStatusListenerPerApp.get(packageName); 2723 if (listenersTracker == null || listenersTracker.getRegisteredCallbackCount() == 0) { 2724 return; 2725 } 2726 2727 if (mVerboseLoggingEnabled) { 2728 Log.v(TAG, "Sending user approval status change event to " + packageName); 2729 } 2730 int itemCount = listenersTracker.beginBroadcast(); 2731 for (int i = 0; i < itemCount; i++) { 2732 try { 2733 listenersTracker.getBroadcastItem(i).onUserApprovalStatusChange( 2734 getNetworkSuggestionUserApprovalStatus(uid, packageName)); 2735 } catch (RemoteException e) { 2736 Log.e(TAG, "sendUserApprovalStatusChange: remote exception -- " + e); 2737 } 2738 } 2739 listenersTracker.finishBroadcast(); 2740 } 2741 areCarrierMergedSuggestionsAllowed(WifiConfiguration config, String packageName)2742 private boolean areCarrierMergedSuggestionsAllowed(WifiConfiguration config, 2743 String packageName) { 2744 if (isAppWorkingAsCrossCarrierProvider(packageName)) { 2745 return true; 2746 } 2747 int subId = config.subscriptionId; 2748 if (config.getSubscriptionGroup() != null) { 2749 subId = mWifiCarrierInfoManager.getActiveSubscriptionIdInGroup( 2750 config.getSubscriptionGroup()); 2751 } 2752 2753 return mWifiCarrierInfoManager.areMergedCarrierWifiNetworksAllowed(subId); 2754 } 2755 2756 /** 2757 * Dump of {@link WifiNetworkSuggestionsManager}. 2758 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)2759 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 2760 pw.println("Dump of WifiNetworkSuggestionsManager"); 2761 pw.println("WifiNetworkSuggestionsManager - Networks Begin ----"); 2762 for (Map.Entry<String, PerAppInfo> networkSuggestionsEntry 2763 : mActiveNetworkSuggestionsPerApp.entrySet()) { 2764 pw.println("Package Name: " + networkSuggestionsEntry.getKey()); 2765 PerAppInfo appInfo = networkSuggestionsEntry.getValue(); 2766 pw.println("Has user approved: " + appInfo.hasUserApproved); 2767 pw.println("Has carrier privileges: " 2768 + (appInfo.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID)); 2769 for (ExtendedWifiNetworkSuggestion extNetworkSuggestion 2770 : appInfo.extNetworkSuggestions.values()) { 2771 pw.println("Network: " + extNetworkSuggestion); 2772 } 2773 } 2774 pw.println("WifiNetworkSuggestionsManager - Networks End ----"); 2775 } 2776 resetNotification()2777 public void resetNotification() { 2778 mNotificationManager.cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE); 2779 mNotificationUpdateTime = 0; 2780 } 2781 getLingerDelayMs()2782 private int getLingerDelayMs() { 2783 return SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS); 2784 } 2785 onSecurityParamsUpdateForSuggestion(WifiConfiguration config, List<SecurityParams> securityParams)2786 private void onSecurityParamsUpdateForSuggestion(WifiConfiguration config, 2787 List<SecurityParams> securityParams) { 2788 Set<ExtendedWifiNetworkSuggestion> matchingExtendedWifiNetworkSuggestions = 2789 getNetworkSuggestionsForWifiConfiguration(config, config.BSSID); 2790 if (matchingExtendedWifiNetworkSuggestions == null 2791 || matchingExtendedWifiNetworkSuggestions.isEmpty()) { 2792 if (mVerboseLoggingEnabled) { 2793 Log.w(TAG, "onSecurityParamsUpdateForSuggestion: no network matches: " + config); 2794 } 2795 return; 2796 } 2797 for (ExtendedWifiNetworkSuggestion ewns : matchingExtendedWifiNetworkSuggestions) { 2798 removeFromScanResultMatchInfoMapAndRemoveRelatedScoreCard(ewns, false); 2799 ewns.wns.wifiConfiguration.setSecurityParams(securityParams); 2800 addToScanResultMatchInfoMap(ewns); 2801 } 2802 saveToStore(); 2803 } 2804 } 2805