| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.wifi; |
| |
| import static android.app.AppOpsManager.MODE_IGNORED; |
| import static android.app.AppOpsManager.OPSTR_CHANGE_WIFI_STATE; |
| |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.SuppressLint; |
| import android.app.ActivityManager; |
| import android.app.AlertDialog; |
| import android.app.AppOpsManager; |
| import android.app.Notification; |
| import android.app.PendingIntent; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.res.Resources; |
| import android.graphics.drawable.Icon; |
| import android.net.MacAddress; |
| import android.net.NetworkScoreManager; |
| import android.net.wifi.ISuggestionConnectionStatusListener; |
| import android.net.wifi.ISuggestionUserApprovalStatusListener; |
| import android.net.wifi.ScanResult; |
| import android.net.wifi.WifiConfiguration; |
| import android.net.wifi.WifiEnterpriseConfig; |
| import android.net.wifi.WifiManager; |
| import android.net.wifi.WifiNetworkSuggestion; |
| import android.net.wifi.WifiScanner; |
| import android.net.wifi.hotspot2.PasspointConfiguration; |
| import android.os.Handler; |
| import android.os.Process; |
| import android.os.RemoteCallbackList; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyManager; |
| import android.text.TextUtils; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.Log; |
| import android.util.Pair; |
| import android.view.WindowManager; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; |
| import com.android.modules.utils.build.SdkLevel; |
| import com.android.server.wifi.util.LruConnectionTracker; |
| import com.android.server.wifi.util.WifiPermissionsUtil; |
| import com.android.wifi.resources.R; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| import javax.annotation.concurrent.NotThreadSafe; |
| |
| /** |
| * Network Suggestions Manager. |
| * NOTE: This class should always be invoked from the main wifi service thread. |
| */ |
| @NotThreadSafe |
| @SuppressLint("LongLogTag") |
| public class WifiNetworkSuggestionsManager { |
| private static final String TAG = "WifiNetworkSuggestionsManager"; |
| |
| /** Intent when user tapped action button to allow the app. */ |
| @VisibleForTesting |
| public static final String NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION = |
| "com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_APP"; |
| /** Intent when user tapped action button to disallow the app. */ |
| @VisibleForTesting |
| public static final String NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION = |
| "com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_APP"; |
| /** Intent when user dismissed the notification. */ |
| @VisibleForTesting |
| public static final String NOTIFICATION_USER_DISMISSED_INTENT_ACTION = |
| "com.android.server.wifi.action.NetworkSuggestion.USER_DISMISSED"; |
| @VisibleForTesting |
| public static final String EXTRA_PACKAGE_NAME = |
| "com.android.server.wifi.extra.NetworkSuggestion.PACKAGE_NAME"; |
| @VisibleForTesting |
| public static final String EXTRA_UID = |
| "com.android.server.wifi.extra.NetworkSuggestion.UID"; |
| |
| public static final int APP_TYPE_CARRIER_PRIVILEGED = 1; |
| public static final int APP_TYPE_NETWORK_PROVISIONING = 2; |
| public static final int APP_TYPE_NON_PRIVILEGED = 3; |
| |
| public static final int ACTION_USER_ALLOWED_APP = 1; |
| public static final int ACTION_USER_DISALLOWED_APP = 2; |
| public static final int ACTION_USER_DISMISS = 3; |
| |
| public static final int DEFAULT_PRIORITY_GROUP = 0; |
| |
| @IntDef(prefix = { "ACTION_USER_" }, value = { |
| ACTION_USER_ALLOWED_APP, |
| ACTION_USER_DISALLOWED_APP, |
| ACTION_USER_DISMISS |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface UserActionCode { } |
| |
| /** |
| * Limit number of hidden networks attach to scan |
| */ |
| private static final int NUMBER_OF_HIDDEN_NETWORK_FOR_ONE_SCAN = 100; |
| /** |
| * Expiration timeout for user notification in milliseconds. (15 min) |
| */ |
| private static final long NOTIFICATION_EXPIRY_MILLS = 15 * 60 * 1000; |
| /** |
| * Notification update delay in milliseconds. (10 min) |
| */ |
| private static final long NOTIFICATION_UPDATE_DELAY_MILLS = 10 * 60 * 1000; |
| |
| private final WifiContext mContext; |
| private final Resources mResources; |
| private final Handler mHandler; |
| private final AppOpsManager mAppOps; |
| private final ActivityManager mActivityManager; |
| private final WifiNotificationManager mNotificationManager; |
| private final NetworkScoreManager mNetworkScoreManager; |
| private final PackageManager mPackageManager; |
| private final WifiPermissionsUtil mWifiPermissionsUtil; |
| private final WifiConfigManager mWifiConfigManager; |
| private final WifiMetrics mWifiMetrics; |
| private final WifiInjector mWifiInjector; |
| private final FrameworkFacade mFrameworkFacade; |
| private final WifiCarrierInfoManager mWifiCarrierInfoManager; |
| private final WifiKeyStore mWifiKeyStore; |
| private final Clock mClock; |
| // Keep order of network connection. |
| private final LruConnectionTracker mLruConnectionTracker; |
| |
| private class OnNetworkUpdateListener implements |
| WifiConfigManager.OnNetworkUpdateListener { |
| @Override |
| public void onConnectChoiceSet(@NonNull List<WifiConfiguration> networks, |
| String choiceKey, int rssi) { |
| onUserConnectChoiceSet(networks, choiceKey, rssi); |
| } |
| @Override |
| public void onConnectChoiceRemoved(String choiceKey) { |
| onUserConnectChoiceRemove(choiceKey); |
| } |
| } |
| |
| /** |
| * Per app meta data to store network suggestions, status, etc for each app providing network |
| * suggestions on the device. |
| */ |
| public static class PerAppInfo { |
| /** |
| * UID of the app. |
| */ |
| public int uid; |
| /** |
| * Package Name of the app. |
| */ |
| public final String packageName; |
| /** |
| * First Feature in the package that registered the suggestion |
| */ |
| public final String featureId; |
| /** |
| 97 * Map of active network suggestions provided by the app keyed by hashcode. |
| */ |
| public final Map<Integer, ExtendedWifiNetworkSuggestion> extNetworkSuggestions = |
| new ArrayMap<>(); |
| /** |
| * Whether we have shown the user a notification for this app. |
| */ |
| public boolean hasUserApproved = false; |
| /** |
| * Carrier Id of SIM which give app carrier privileges. |
| */ |
| public int carrierId = TelephonyManager.UNKNOWN_CARRIER_ID; |
| |
| /** Stores the max size of the {@link #extNetworkSuggestions} list ever for this app */ |
| public int maxSize = 0; |
| |
| public PerAppInfo(int uid, @NonNull String packageName, @Nullable String featureId) { |
| this.uid = uid; |
| this.packageName = packageName; |
| this.featureId = featureId; |
| } |
| |
| /** |
| * Needed for migration of config store data. |
| */ |
| public void setUid(int uid) { |
| if (this.uid == Process.INVALID_UID) { |
| this.uid = uid; |
| } |
| // else ignored. |
| } |
| |
| /** |
| * Needed when a normal App became carrier privileged when SIM insert |
| */ |
| public void setCarrierId(int carrierId) { |
| if (this.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) { |
| this.carrierId = carrierId; |
| } |
| // else ignored. |
| } |
| |
| /** |
| * Returns true if this app has the necessary approvals to place network suggestions. |
| */ |
| private boolean isApproved(@Nullable String activeScorerPkg) { |
| return hasUserApproved || isExemptFromUserApproval(activeScorerPkg); |
| } |
| |
| /** |
| * Returns true if this app can suggest networks without user approval. |
| */ |
| private boolean isExemptFromUserApproval(@Nullable String activeScorerPkg) { |
| final boolean isCarrierPrivileged = carrierId != TelephonyManager.UNKNOWN_CARRIER_ID; |
| if (isCarrierPrivileged) { |
| return true; |
| } |
| return packageName.equals(activeScorerPkg); |
| } |
| |
| // This is only needed for comparison in unit tests. |
| @Override |
| public boolean equals(Object other) { |
| if (other == null) return false; |
| if (!(other instanceof PerAppInfo)) return false; |
| PerAppInfo otherPerAppInfo = (PerAppInfo) other; |
| return uid == otherPerAppInfo.uid |
| && TextUtils.equals(packageName, otherPerAppInfo.packageName) |
| && Objects.equals(extNetworkSuggestions, otherPerAppInfo.extNetworkSuggestions) |
| && hasUserApproved == otherPerAppInfo.hasUserApproved; |
| } |
| |
| // This is only needed for comparison in unit tests. |
| @Override |
| public int hashCode() { |
| return Objects.hash(uid, packageName, extNetworkSuggestions, hasUserApproved); |
| } |
| |
| @Override |
| public String toString() { |
| return new StringBuilder("PerAppInfo[ ") |
| .append("uid=").append(uid) |
| .append(", packageName=").append(packageName) |
| .append(", hasUserApproved=").append(hasUserApproved) |
| .append(", suggestions=").append(extNetworkSuggestions) |
| .append(" ]") |
| .toString(); |
| } |
| } |
| |
| /** |
| * Internal container class which holds a network suggestion and a pointer to the |
| * {@link PerAppInfo} entry from {@link #mActiveNetworkSuggestionsPerApp} corresponding to the |
| * app that made the suggestion. |
| */ |
| public static class ExtendedWifiNetworkSuggestion { |
| public final WifiNetworkSuggestion wns; |
| // Store the pointer to the corresponding app's meta data. |
| public final PerAppInfo perAppInfo; |
| public boolean isAutojoinEnabled; |
| public String anonymousIdentity = null; |
| public String connectChoice = null; |
| public int connectChoiceRssi = 0; |
| |
| public ExtendedWifiNetworkSuggestion(@NonNull WifiNetworkSuggestion wns, |
| @NonNull PerAppInfo perAppInfo, |
| boolean isAutoJoinEnabled) { |
| this.wns = wns; |
| this.perAppInfo = perAppInfo; |
| this.isAutojoinEnabled = isAutoJoinEnabled; |
| this.wns.wifiConfiguration.fromWifiNetworkSuggestion = true; |
| this.wns.wifiConfiguration.ephemeral = true; |
| this.wns.wifiConfiguration.creatorName = perAppInfo.packageName; |
| this.wns.wifiConfiguration.creatorUid = perAppInfo.uid; |
| if (perAppInfo.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) { |
| return; |
| } |
| // If App is carrier privileged, set carrier Id to the profile. |
| this.wns.wifiConfiguration.carrierId = perAppInfo.carrierId; |
| if (this.wns.passpointConfiguration != null) { |
| this.wns.passpointConfiguration.setCarrierId(perAppInfo.carrierId); |
| } |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(wns, perAppInfo.uid, perAppInfo.packageName); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (!(obj instanceof ExtendedWifiNetworkSuggestion)) { |
| return false; |
| } |
| ExtendedWifiNetworkSuggestion other = (ExtendedWifiNetworkSuggestion) obj; |
| return wns.equals(other.wns) |
| && perAppInfo.uid == other.perAppInfo.uid |
| && TextUtils.equals(perAppInfo.packageName, other.perAppInfo.packageName); |
| } |
| |
| @Override |
| public String toString() { |
| return new StringBuilder(wns.toString()) |
| .append(", isAutoJoinEnabled=").append(isAutojoinEnabled) |
| .toString(); |
| } |
| |
| /** |
| * Convert from {@link WifiNetworkSuggestion} to a new instance of |
| * {@link ExtendedWifiNetworkSuggestion}. |
| */ |
| public static ExtendedWifiNetworkSuggestion fromWns(@NonNull WifiNetworkSuggestion wns, |
| @NonNull PerAppInfo perAppInfo, boolean isAutoJoinEnabled) { |
| return new ExtendedWifiNetworkSuggestion(wns, perAppInfo, isAutoJoinEnabled); |
| } |
| |
| /** |
| * Create a {@link WifiConfiguration} from suggestion for framework internal use. |
| */ |
| public WifiConfiguration createInternalWifiConfiguration( |
| @Nullable WifiCarrierInfoManager carrierInfoManager) { |
| WifiConfiguration config = new WifiConfiguration(wns.getWifiConfiguration()); |
| config.allowAutojoin = isAutojoinEnabled; |
| if (config.enterpriseConfig |
| != null && config.enterpriseConfig.isAuthenticationSimBased()) { |
| config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity); |
| } |
| config.getNetworkSelectionStatus().setConnectChoice(connectChoice); |
| config.getNetworkSelectionStatus().setConnectChoiceRssi(connectChoiceRssi); |
| if (carrierInfoManager != null) { |
| config.subscriptionId = carrierInfoManager.getBestMatchSubscriptionId(config); |
| // shouldDisableMacRandomization checks if the SSID matches with a SSID configured |
| // in CarrierConfigManger for the provided subscriptionId. |
| if (carrierInfoManager.shouldDisableMacRandomization(config.SSID, |
| config.carrierId, config.subscriptionId)) { |
| Log.i(TAG, "Disabling MAC randomization on " + config.SSID |
| + " due to CarrierConfig override"); |
| config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE; |
| } |
| } |
| return config; |
| } |
| } |
| |
| /** |
| * Map of package name of an app to the set of active network suggestions provided by the app. |
| */ |
| private final Map<String, PerAppInfo> mActiveNetworkSuggestionsPerApp = new HashMap<>(); |
| /** |
| * Map of package name of an app to the app ops changed listener for the app. |
| */ |
| private final Map<String, AppOpsChangedListener> mAppOpsChangedListenerPerApp = new HashMap<>(); |
| /** |
| * Map maintained to help lookup all the network suggestions (with no bssid) that match a |
| * provided scan result. |
| * Note: |
| * <li>There could be multiple suggestions (provided by different apps) that match a single |
| * scan result.</li> |
| * <li>Adding/Removing to this set for scan result lookup is expensive. But, we expect scan |
| * result lookup to happen much more often than apps modifying network suggestions.</li> |
| */ |
| private final Map<ScanResultMatchInfo, Set<ExtendedWifiNetworkSuggestion>> |
| mActiveScanResultMatchInfoWithNoBssid = new HashMap<>(); |
| /** |
| * Map maintained to help lookup all the network suggestions (with bssid) that match a provided |
| * scan result. |
| * Note: |
| * <li>There could be multiple suggestions (provided by different apps) that match a single |
| * scan result.</li> |
| * <li>Adding/Removing to this set for scan result lookup is expensive. But, we expect scan |
| * result lookup to happen much more often than apps modifying network suggestions.</li> |
| */ |
| private final Map<Pair<ScanResultMatchInfo, MacAddress>, Set<ExtendedWifiNetworkSuggestion>> |
| mActiveScanResultMatchInfoWithBssid = new HashMap<>(); |
| |
| private final Map<String, Set<ExtendedWifiNetworkSuggestion>> |
| mPasspointInfo = new HashMap<>(); |
| |
| private final HashMap<String, RemoteCallbackList<ISuggestionConnectionStatusListener>> |
| mSuggestionStatusListenerPerApp = new HashMap<>(); |
| |
| private final HashMap<String, RemoteCallbackList<ISuggestionUserApprovalStatusListener>> |
| mSuggestionUserApprovalStatusListenerPerApp = new HashMap<>(); |
| |
| /** |
| * Store the suggestion update listeners. |
| */ |
| private final List<OnSuggestionUpdateListener> mListeners = new ArrayList<>(); |
| |
| /** |
| * Intent filter for processing notification actions. |
| */ |
| private final IntentFilter mIntentFilter; |
| |
| /** |
| * Verbose logging flag. |
| */ |
| private boolean mVerboseLoggingEnabled = false; |
| /** |
| * Indicates that we have new data to serialize. |
| */ |
| private boolean mHasNewDataToSerialize = false; |
| /** |
| * The {@link Clock#getElapsedSinceBootMillis()} must be at least this value for us |
| * to update/show the notification. |
| */ |
| private long mNotificationUpdateTime; |
| |
| private boolean mIsLastUserApprovalUiDialog = false; |
| |
| private boolean mUserDataLoaded = false; |
| |
| /** |
| * Keep a set of packageNames which is treated as carrier provider. |
| */ |
| private final Set<String> mCrossCarrierProvidersSet = new ArraySet<>(); |
| |
| /** |
| * Listener for app-ops changes for active suggestor apps. |
| */ |
| private final class AppOpsChangedListener implements AppOpsManager.OnOpChangedListener { |
| private final String mPackageName; |
| private final int mUid; |
| |
| AppOpsChangedListener(@NonNull String packageName, int uid) { |
| mPackageName = packageName; |
| mUid = uid; |
| } |
| |
| @Override |
| public void onOpChanged(String op, String packageName) { |
| mHandler.post(() -> { |
| if (!mPackageName.equals(packageName)) return; |
| if (!OPSTR_CHANGE_WIFI_STATE.equals(op)) return; |
| |
| // Ensure the uid to package mapping is still correct. |
| try { |
| mAppOps.checkPackage(mUid, mPackageName); |
| } catch (SecurityException e) { |
| Log.wtf(TAG, "Invalid uid/package" + packageName); |
| return; |
| } |
| |
| if (mAppOps.unsafeCheckOpNoThrow(OPSTR_CHANGE_WIFI_STATE, mUid, mPackageName) |
| == AppOpsManager.MODE_IGNORED) { |
| Log.i(TAG, "User disallowed change wifi state for " + packageName); |
| // User disabled the app, remove app from database. We want the notification |
| // again if the user enabled the app-op back. |
| removeApp(mPackageName); |
| mWifiMetrics.incrementNetworkSuggestionUserRevokePermission(); |
| } |
| }); |
| } |
| }; |
| |
| /** |
| * Module to interact with the wifi config store. |
| */ |
| private class NetworkSuggestionDataSource implements NetworkSuggestionStoreData.DataSource { |
| @Override |
| public Map<String, PerAppInfo> toSerialize() { |
| for (Map.Entry<String, PerAppInfo> entry : mActiveNetworkSuggestionsPerApp.entrySet()) { |
| for (ExtendedWifiNetworkSuggestion ewns : entry.getValue().extNetworkSuggestions |
| .values()) { |
| if (ewns.wns.passpointConfiguration != null) { |
| continue; |
| } |
| ewns.wns.wifiConfiguration.isMostRecentlyConnected = mLruConnectionTracker |
| .isMostRecentlyConnected(ewns.createInternalWifiConfiguration( |
| mWifiCarrierInfoManager)); |
| } |
| } |
| // Clear the flag after writing to disk. |
| // TODO(b/115504887): Don't reset the flag on write failure. |
| mHasNewDataToSerialize = false; |
| return mActiveNetworkSuggestionsPerApp; |
| } |
| |
| @Override |
| public void fromDeserialized(Map<String, PerAppInfo> networkSuggestionsMap) { |
| mActiveNetworkSuggestionsPerApp.clear(); |
| mActiveNetworkSuggestionsPerApp.putAll(networkSuggestionsMap); |
| // Build the scan cache. |
| for (Map.Entry<String, PerAppInfo> entry : networkSuggestionsMap.entrySet()) { |
| String packageName = entry.getKey(); |
| Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = |
| entry.getValue().extNetworkSuggestions.values(); |
| if (!extNetworkSuggestions.isEmpty()) { |
| // Start tracking app-op changes from the app if they have active suggestions. |
| startTrackingAppOpsChange(packageName, |
| extNetworkSuggestions.iterator().next().perAppInfo.uid); |
| } |
| for (ExtendedWifiNetworkSuggestion ewns : extNetworkSuggestions) { |
| if (ewns.wns.passpointConfiguration != null) { |
| addToPasspointInfoMap(ewns); |
| } else { |
| if (ewns.wns.wifiConfiguration.isMostRecentlyConnected) { |
| mLruConnectionTracker |
| .addNetwork(ewns.createInternalWifiConfiguration( |
| mWifiCarrierInfoManager)); |
| } |
| addToScanResultMatchInfoMap(ewns); |
| } |
| } |
| } |
| mUserDataLoaded = true; |
| } |
| |
| @Override |
| public void reset() { |
| mUserDataLoaded = false; |
| mActiveNetworkSuggestionsPerApp.clear(); |
| mActiveScanResultMatchInfoWithBssid.clear(); |
| mActiveScanResultMatchInfoWithNoBssid.clear(); |
| mPasspointInfo.clear(); |
| } |
| |
| @Override |
| public boolean hasNewDataToSerialize() { |
| return mHasNewDataToSerialize; |
| } |
| } |
| |
| private void handleUserAllowAction(int uid, String packageName) { |
| Log.i(TAG, "User clicked to allow app"); |
| // Set the user approved flag. |
| setHasUserApprovedForApp(true, uid, packageName); |
| mNotificationUpdateTime = 0; |
| mWifiMetrics.addUserApprovalSuggestionAppUiReaction( |
| ACTION_USER_ALLOWED_APP, |
| mIsLastUserApprovalUiDialog); |
| } |
| |
| private void handleUserDisallowAction(int uid, String packageName) { |
| Log.i(TAG, "User clicked to disallow app"); |
| // Take away CHANGE_WIFI_STATE app-ops from the app. |
| mAppOps.setMode(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, uid, packageName, |
| MODE_IGNORED); |
| // Set the user approved flag. |
| setHasUserApprovedForApp(false, uid, packageName); |
| mNotificationUpdateTime = 0; |
| mWifiMetrics.addUserApprovalSuggestionAppUiReaction( |
| ACTION_USER_DISALLOWED_APP, |
| mIsLastUserApprovalUiDialog); |
| } |
| |
| private void handleUserDismissAction() { |
| Log.i(TAG, "User dismissed the notification"); |
| mNotificationUpdateTime = 0; |
| mWifiMetrics.addUserApprovalSuggestionAppUiReaction( |
| ACTION_USER_DISMISS, |
| mIsLastUserApprovalUiDialog); |
| } |
| |
| private final BroadcastReceiver mBroadcastReceiver = |
| new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME); |
| int uid = intent.getIntExtra(EXTRA_UID, -1); |
| if (packageName == null || uid == -1) { |
| Log.e(TAG, "No package name or uid found in intent"); |
| return; |
| } |
| switch (intent.getAction()) { |
| case NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION: |
| handleUserAllowAction(uid, packageName); |
| break; |
| case NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION: |
| handleUserDisallowAction(uid, packageName); |
| break; |
| case NOTIFICATION_USER_DISMISSED_INTENT_ACTION: |
| handleUserDismissAction(); |
| return; // no need to cancel a dismissed notification, return. |
| default: |
| Log.e(TAG, "Unknown action " + intent.getAction()); |
| return; |
| } |
| // Clear notification once the user interacts with it. |
| mNotificationManager.cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE); |
| } |
| }; |
| |
| /** |
| * Interface for other modules to listen to the suggestion updated events. |
| */ |
| public interface OnSuggestionUpdateListener { |
| /** |
| * Invoked on suggestion being added or updated. |
| */ |
| void onSuggestionsAddedOrUpdated(@NonNull List<WifiNetworkSuggestion> addedSuggestions); |
| /** |
| * Invoked on suggestion being removed. |
| */ |
| void onSuggestionsRemoved(@NonNull List<WifiNetworkSuggestion> removedSuggestions); |
| } |
| |
| private final class UserApproveCarrierListener implements |
| WifiCarrierInfoManager.OnUserApproveCarrierListener { |
| |
| @Override |
| public void onUserAllowed(int carrierId) { |
| restoreInitialAutojoinForCarrierId(carrierId); |
| } |
| } |
| |
| public WifiNetworkSuggestionsManager(WifiContext context, Handler handler, |
| WifiInjector wifiInjector, WifiPermissionsUtil wifiPermissionsUtil, |
| WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore, |
| WifiMetrics wifiMetrics, WifiCarrierInfoManager wifiCarrierInfoManager, |
| WifiKeyStore keyStore, LruConnectionTracker lruConnectionTracker, |
| Clock clock) { |
| mContext = context; |
| mResources = context.getResources(); |
| mHandler = handler; |
| mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); |
| mActivityManager = context.getSystemService(ActivityManager.class); |
| mNetworkScoreManager = context.getSystemService(NetworkScoreManager.class); |
| mPackageManager = context.getPackageManager(); |
| mWifiInjector = wifiInjector; |
| mFrameworkFacade = mWifiInjector.getFrameworkFacade(); |
| mWifiPermissionsUtil = wifiPermissionsUtil; |
| mWifiConfigManager = wifiConfigManager; |
| mWifiMetrics = wifiMetrics; |
| mWifiCarrierInfoManager = wifiCarrierInfoManager; |
| mWifiKeyStore = keyStore; |
| mNotificationManager = mWifiInjector.getWifiNotificationManager(); |
| mClock = clock; |
| |
| // register the data store for serializing/deserializing data. |
| wifiConfigStore.registerStoreData( |
| wifiInjector.makeNetworkSuggestionStoreData(new NetworkSuggestionDataSource())); |
| |
| mWifiCarrierInfoManager.addImsiExemptionUserApprovalListener( |
| new UserApproveCarrierListener()); |
| |
| // Register broadcast receiver for UI interactions. |
| mIntentFilter = new IntentFilter(); |
| mIntentFilter.addAction(NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION); |
| mIntentFilter.addAction(NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION); |
| mIntentFilter.addAction(NOTIFICATION_USER_DISMISSED_INTENT_ACTION); |
| |
| mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, handler); |
| mLruConnectionTracker = lruConnectionTracker; |
| mWifiConfigManager.addOnNetworkUpdateListener( |
| new WifiNetworkSuggestionsManager.OnNetworkUpdateListener()); |
| } |
| |
| /** |
| * Enable verbose logging. |
| */ |
| public void enableVerboseLogging(int verbose) { |
| mVerboseLoggingEnabled = verbose > 0; |
| } |
| |
| private void saveToStore() { |
| // Set the flag to let WifiConfigStore that we have new data to write. |
| mHasNewDataToSerialize = true; |
| if (!mWifiConfigManager.saveToStore(true)) { |
| Log.w(TAG, "Failed to save to store"); |
| } |
| } |
| |
| private void addToScanResultMatchInfoMap( |
| @NonNull ExtendedWifiNetworkSuggestion extNetworkSuggestion) { |
| ScanResultMatchInfo scanResultMatchInfo = |
| ScanResultMatchInfo.fromWifiConfiguration( |
| extNetworkSuggestion.wns.wifiConfiguration); |
| Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsForScanResultMatchInfo; |
| if (!TextUtils.isEmpty(extNetworkSuggestion.wns.wifiConfiguration.BSSID)) { |
| Pair<ScanResultMatchInfo, MacAddress> lookupPair = |
| Pair.create(scanResultMatchInfo, |
| MacAddress.fromString( |
| extNetworkSuggestion.wns.wifiConfiguration.BSSID)); |
| extNetworkSuggestionsForScanResultMatchInfo = |
| mActiveScanResultMatchInfoWithBssid.get(lookupPair); |
| if (extNetworkSuggestionsForScanResultMatchInfo == null) { |
| extNetworkSuggestionsForScanResultMatchInfo = new HashSet<>(); |
| mActiveScanResultMatchInfoWithBssid.put( |
| lookupPair, extNetworkSuggestionsForScanResultMatchInfo); |
| } |
| } else { |
| extNetworkSuggestionsForScanResultMatchInfo = |
| mActiveScanResultMatchInfoWithNoBssid.get(scanResultMatchInfo); |
| if (extNetworkSuggestionsForScanResultMatchInfo == null) { |
| extNetworkSuggestionsForScanResultMatchInfo = new HashSet<>(); |
| mActiveScanResultMatchInfoWithNoBssid.put( |
| scanResultMatchInfo, extNetworkSuggestionsForScanResultMatchInfo); |
| } |
| } |
| extNetworkSuggestionsForScanResultMatchInfo.remove(extNetworkSuggestion); |
| extNetworkSuggestionsForScanResultMatchInfo.add(extNetworkSuggestion); |
| } |
| |
| private void removeFromScanResultMatchInfoMapAndRemoveRelatedScoreCard( |
| @NonNull ExtendedWifiNetworkSuggestion extNetworkSuggestion) { |
| ScanResultMatchInfo scanResultMatchInfo = |
| ScanResultMatchInfo.fromWifiConfiguration( |
| extNetworkSuggestion.wns.wifiConfiguration); |
| Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsForScanResultMatchInfo; |
| if (!TextUtils.isEmpty(extNetworkSuggestion.wns.wifiConfiguration.BSSID)) { |
| Pair<ScanResultMatchInfo, MacAddress> lookupPair = |
| Pair.create(scanResultMatchInfo, |
| MacAddress.fromString( |
| extNetworkSuggestion.wns.wifiConfiguration.BSSID)); |
| extNetworkSuggestionsForScanResultMatchInfo = |
| mActiveScanResultMatchInfoWithBssid.get(lookupPair); |
| // This should never happen because we should have done necessary error checks in |
| // the parent method. |
| if (extNetworkSuggestionsForScanResultMatchInfo == null) { |
| Log.wtf(TAG, "No scan result match info found."); |
| return; |
| } |
| extNetworkSuggestionsForScanResultMatchInfo.remove(extNetworkSuggestion); |
| // Remove the set from map if empty. |
| if (extNetworkSuggestionsForScanResultMatchInfo.isEmpty()) { |
| mActiveScanResultMatchInfoWithBssid.remove(lookupPair); |
| if (!mActiveScanResultMatchInfoWithNoBssid.containsKey(scanResultMatchInfo)) { |
| removeNetworkFromScoreCard(extNetworkSuggestion.wns.wifiConfiguration); |
| mLruConnectionTracker.removeNetwork( |
| extNetworkSuggestion.wns.wifiConfiguration); |
| } |
| } |
| } else { |
| extNetworkSuggestionsForScanResultMatchInfo = |
| mActiveScanResultMatchInfoWithNoBssid.get(scanResultMatchInfo); |
| // This should never happen because we should have done necessary error checks in |
| // the parent method. |
| if (extNetworkSuggestionsForScanResultMatchInfo == null) { |
| Log.wtf(TAG, "No scan result match info found."); |
| return; |
| } |
| extNetworkSuggestionsForScanResultMatchInfo.remove(extNetworkSuggestion); |
| // Remove the set from map if empty. |
| if (extNetworkSuggestionsForScanResultMatchInfo.isEmpty()) { |
| mActiveScanResultMatchInfoWithNoBssid.remove(scanResultMatchInfo); |
| removeNetworkFromScoreCard(extNetworkSuggestion.wns.wifiConfiguration); |
| mLruConnectionTracker.removeNetwork( |
| extNetworkSuggestion.wns.wifiConfiguration); |
| } |
| } |
| } |
| |
| private void removeNetworkFromScoreCard(WifiConfiguration wifiConfiguration) { |
| WifiConfiguration existing = |
| mWifiConfigManager.getConfiguredNetwork(wifiConfiguration.getProfileKey()); |
| // If there is a saved network, do not remove from the score card. |
| if (existing != null && !existing.fromWifiNetworkSuggestion) { |
| return; |
| } |
| mWifiInjector.getWifiScoreCard().removeNetwork(wifiConfiguration.SSID); |
| } |
| |
| private void addToPasspointInfoMap(ExtendedWifiNetworkSuggestion ewns) { |
| Set<ExtendedWifiNetworkSuggestion> extendedWifiNetworkSuggestions = |
| mPasspointInfo.get(ewns.wns.wifiConfiguration.FQDN); |
| if (extendedWifiNetworkSuggestions == null) { |
| extendedWifiNetworkSuggestions = new HashSet<>(); |
| } |
| extendedWifiNetworkSuggestions.remove(ewns); |
| extendedWifiNetworkSuggestions.add(ewns); |
| mPasspointInfo.put(ewns.wns.wifiConfiguration.FQDN, extendedWifiNetworkSuggestions); |
| } |
| |
| private void removeFromPassPointInfoMap(ExtendedWifiNetworkSuggestion ewns) { |
| Set<ExtendedWifiNetworkSuggestion> extendedWifiNetworkSuggestions = |
| mPasspointInfo.get(ewns.wns.wifiConfiguration.FQDN); |
| if (extendedWifiNetworkSuggestions == null |
| || !extendedWifiNetworkSuggestions.contains(ewns)) { |
| Log.wtf(TAG, "No Passpoint info found."); |
| return; |
| } |
| extendedWifiNetworkSuggestions.remove(ewns); |
| if (extendedWifiNetworkSuggestions.isEmpty()) { |
| mPasspointInfo.remove(ewns.wns.wifiConfiguration.FQDN); |
| } |
| } |
| |
| private void startTrackingAppOpsChange(@NonNull String packageName, int uid) { |
| AppOpsChangedListener appOpsChangedListener = |
| new AppOpsChangedListener(packageName, uid); |
| mAppOps.startWatchingMode(OPSTR_CHANGE_WIFI_STATE, packageName, appOpsChangedListener); |
| mAppOpsChangedListenerPerApp.put(packageName, appOpsChangedListener); |
| } |
| |
| /** |
| * Helper method to convert the incoming collection of public {@link WifiNetworkSuggestion} |
| * objects to a set of corresponding internal wrapper |
| * {@link ExtendedWifiNetworkSuggestion} objects. |
| */ |
| private Set<ExtendedWifiNetworkSuggestion> convertToExtendedWnsSet( |
| final Collection<WifiNetworkSuggestion> networkSuggestions, |
| final PerAppInfo perAppInfo) { |
| return networkSuggestions |
| .stream() |
| .map(n -> ExtendedWifiNetworkSuggestion.fromWns(n, perAppInfo, |
| n.isInitialAutoJoinEnabled)) |
| .collect(Collectors.toSet()); |
| } |
| |
| /** |
| * Helper method to convert the incoming collection of internal wrapper |
| * {@link ExtendedWifiNetworkSuggestion} objects to a set of corresponding public |
| * {@link WifiNetworkSuggestion} objects. |
| */ |
| private Set<WifiNetworkSuggestion> convertToWnsSet( |
| final Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions) { |
| return extNetworkSuggestions |
| .stream() |
| .map(n -> n.wns) |
| .collect(Collectors.toSet()); |
| } |
| |
| private void updateWifiConfigInWcmIfPresent( |
| WifiConfiguration newConfig, int uid, String packageName) { |
| WifiConfiguration configInWcm = |
| mWifiConfigManager.getConfiguredNetwork(newConfig.getProfileKey()); |
| if (configInWcm == null) return; |
| // !suggestion |
| if (!configInWcm.fromWifiNetworkSuggestion) return; |
| // is suggestion from same app. |
| if (configInWcm.creatorUid != uid |
| || !TextUtils.equals(configInWcm.creatorName, packageName)) { |
| return; |
| } |
| NetworkUpdateResult result = mWifiConfigManager.addOrUpdateNetwork( |
| newConfig, uid, packageName); |
| if (!result.isSuccess()) { |
| Log.e(TAG, "Failed to update config in WifiConfigManager"); |
| return; |
| } |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "Updated config in WifiConfigManager"); |
| } |
| mWifiConfigManager.allowAutojoin(result.getNetworkId(), newConfig.allowAutojoin); |
| } |
| |
| /** |
| * Add the provided list of network suggestions from the corresponding app's active list. |
| */ |
| public @WifiManager.NetworkSuggestionsStatusCode int add( |
| List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName, |
| @Nullable String featureId) { |
| if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(uid)) { |
| Log.e(TAG, "UID " + uid + " not visible to the current user"); |
| return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL; |
| } |
| if (!mUserDataLoaded) { |
| Log.e(TAG, "Add Network suggestion before boot complete is not allowed."); |
| return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL; |
| } |
| if (networkSuggestions == null || networkSuggestions.isEmpty()) { |
| Log.w(TAG, "Empty list of network suggestions for " + packageName + ". Ignoring"); |
| return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; |
| } |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "Adding " + networkSuggestions.size() + " networks from " + packageName); |
| } |
| if (!validateNetworkSuggestions(networkSuggestions, packageName, uid)) { |
| Log.e(TAG, "Invalid suggestion add from app: " + packageName); |
| return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID; |
| } |
| int carrierId = mWifiCarrierInfoManager |
| .getCarrierIdForPackageWithCarrierPrivileges(packageName); |
| if (!validateCarrierNetworkSuggestions(networkSuggestions, uid, packageName, carrierId)) { |
| Log.e(TAG, "bad wifi suggestion from app: " + packageName); |
| return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED; |
| } |
| for (WifiNetworkSuggestion wns : networkSuggestions) { |
| wns.wifiConfiguration.convertLegacyFieldsToSecurityParamsIfNeeded(); |
| if (!WifiConfigurationUtil.addUpgradableSecurityTypeIfNecessary( |
| wns.wifiConfiguration)) { |
| Log.e(TAG, "Invalid suggestion add from app: " + packageName); |
| return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID; |
| } |
| } |
| |
| final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage(); |
| PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName); |
| if (perAppInfo == null) { |
| perAppInfo = new PerAppInfo(uid, packageName, featureId); |
| mActiveNetworkSuggestionsPerApp.put(packageName, perAppInfo); |
| if (mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(uid)) { |
| Log.i(TAG, "Setting the carrier provisioning app approved"); |
| perAppInfo.hasUserApproved = true; |
| mWifiMetrics.incrementNetworkSuggestionApiUsageNumOfAppInType( |
| APP_TYPE_NETWORK_PROVISIONING); |
| } else if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid) |
| || isAppWorkingAsCrossCarrierProvider(packageName)) { |
| // Bypass added for tests & shell commands. |
| Log.i(TAG, "Setting the test app approved"); |
| perAppInfo.hasUserApproved = true; |
| } else if (carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) { |
| Log.i(TAG, "Setting the carrier privileged app approved"); |
| perAppInfo.setCarrierId(carrierId); |
| mWifiMetrics.incrementNetworkSuggestionApiUsageNumOfAppInType( |
| APP_TYPE_CARRIER_PRIVILEGED); |
| } else if (perAppInfo.packageName.equals(activeScorerPackage)) { |
| Log.i(TAG, "Exempting the active scorer app"); |
| // nothing more to do, user approval related checks are done at network selection |
| // time (which also takes care of any dynamic changes in active scorer). |
| mWifiMetrics.incrementNetworkSuggestionApiUsageNumOfAppInType( |
| APP_TYPE_NON_PRIVILEGED); |
| } else { |
| if (isSuggestionFromForegroundApp(packageName)) { |
| sendUserApprovalDialog(packageName, uid); |
| } else { |
| sendUserApprovalNotificationIfNotApproved(packageName, uid); |
| } |
| mWifiMetrics.incrementNetworkSuggestionApiUsageNumOfAppInType( |
| APP_TYPE_NON_PRIVILEGED); |
| } |
| onSuggestionUserApprovalStatusChanged(uid, packageName); |
| } |
| // If PerAppInfo is upgrade from pre-R, uid may not be set. |
| perAppInfo.setUid(uid); |
| // If App became carrier privileged, set the carrier Id. |
| perAppInfo.setCarrierId(carrierId); |
| Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = |
| convertToExtendedWnsSet(networkSuggestions, perAppInfo); |
| boolean isLowRamDevice = mActivityManager.isLowRamDevice(); |
| int networkSuggestionsMaxPerApp = |
| WifiManager.getMaxNumberOfNetworkSuggestionsPerApp(isLowRamDevice); |
| if (perAppInfo.extNetworkSuggestions.size() + extNetworkSuggestions.size() |
| > networkSuggestionsMaxPerApp) { |
| Set<Integer> keySet = extNetworkSuggestions |
| .stream() |
| .map(ExtendedWifiNetworkSuggestion::hashCode) |
| .collect(Collectors.toSet()); |
| Set<Integer> savedKeySet = new HashSet<>(perAppInfo.extNetworkSuggestions.keySet()); |
| savedKeySet.addAll(keySet); |
| if (savedKeySet.size() > networkSuggestionsMaxPerApp) { |
| Log.e(TAG, "Failed to add network suggestions for " + packageName |
| + ". Exceeds max per app, current list size: " |
| + perAppInfo.extNetworkSuggestions.size() |
| + ", new list size: " |
| + extNetworkSuggestions.size()); |
| return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP; |
| } |
| } |
| if (perAppInfo.extNetworkSuggestions.isEmpty()) { |
| // Start tracking app-op changes from the app if they have active suggestions. |
| startTrackingAppOpsChange(packageName, uid); |
| } |
| |
| for (ExtendedWifiNetworkSuggestion ewns: extNetworkSuggestions) { |
| ExtendedWifiNetworkSuggestion oldEwns = perAppInfo.extNetworkSuggestions |
| .get(ewns.hashCode()); |
| // Keep the user connect choice and AnonymousIdentity |
| if (oldEwns != null) { |
| ewns.connectChoice = oldEwns.connectChoice; |
| ewns.connectChoiceRssi = oldEwns.connectChoiceRssi; |
| ewns.anonymousIdentity = oldEwns.anonymousIdentity; |
| // If user change the auto-join, keep the user choice. |
| if (oldEwns.isAutojoinEnabled != oldEwns.wns.isInitialAutoJoinEnabled) { |
| ewns.isAutojoinEnabled = oldEwns.isAutojoinEnabled; |
| } |
| } |
| // If network has no IMSI protection and user didn't approve exemption, make it initial |
| // auto join disabled |
| if (isSimBasedSuggestion(ewns)) { |
| int subId = mWifiCarrierInfoManager |
| .getMatchingSubId(getCarrierIdFromSuggestion(ewns)); |
| if (!(mWifiCarrierInfoManager.requiresImsiEncryption(subId) |
| || mWifiCarrierInfoManager.hasUserApprovedImsiPrivacyExemptionForCarrier( |
| getCarrierIdFromSuggestion(ewns)))) { |
| ewns.isAutojoinEnabled = false; |
| } |
| } |
| mWifiMetrics.addNetworkSuggestionPriorityGroup(ewns.wns.priorityGroup); |
| if (ewns.wns.passpointConfiguration == null) { |
| if (ewns.wns.wifiConfiguration.isEnterprise()) { |
| if (!mWifiKeyStore.updateNetworkKeys(ewns.wns.wifiConfiguration, null)) { |
| Log.e(TAG, "Enterprise network install failure for SSID: " |
| + ewns.wns.wifiConfiguration.SSID); |
| continue; |
| } |
| } |
| // If we have a config in WifiConfigManager for this suggestion, update |
| // WifiConfigManager with the latest WifiConfig. |
| // Note: Similar logic is present in PasspointManager for passpoint networks. |
| updateWifiConfigInWcmIfPresent(ewns.createInternalWifiConfiguration( |
| mWifiCarrierInfoManager), uid, packageName); |
| addToScanResultMatchInfoMap(ewns); |
| } else { |
| ewns.wns.passpointConfiguration.setAutojoinEnabled(ewns.isAutojoinEnabled); |
| // Install Passpoint config, if failure, ignore that suggestion |
| if (!mWifiInjector.getPasspointManager().addOrUpdateProvider( |
| ewns.wns.passpointConfiguration, uid, |
| packageName, true, !ewns.wns.isUntrusted())) { |
| Log.e(TAG, "Passpoint profile install failure for FQDN: " |
| + ewns.wns.wifiConfiguration.FQDN); |
| continue; |
| } |
| addToPasspointInfoMap(ewns); |
| } |
| perAppInfo.extNetworkSuggestions.remove(ewns.hashCode()); |
| perAppInfo.extNetworkSuggestions.put(ewns.hashCode(), ewns); |
| } |
| for (OnSuggestionUpdateListener listener : mListeners) { |
| listener.onSuggestionsAddedOrUpdated(networkSuggestions); |
| } |
| // Update the max size for this app. |
| perAppInfo.maxSize = Math.max(perAppInfo.extNetworkSuggestions.size(), perAppInfo.maxSize); |
| saveToStore(); |
| mWifiMetrics.incrementNetworkSuggestionApiNumModification(); |
| mWifiMetrics.noteNetworkSuggestionApiListSizeHistogram(getAllMaxSizes()); |
| return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; |
| } |
| |
| private int getCarrierIdFromSuggestion(ExtendedWifiNetworkSuggestion ewns) { |
| if (ewns.wns.passpointConfiguration == null) { |
| return ewns.wns.wifiConfiguration.carrierId; |
| } |
| return ewns.wns.passpointConfiguration.getCarrierId(); |
| } |
| |
| private boolean isSimBasedSuggestion(ExtendedWifiNetworkSuggestion ewns) { |
| if (ewns.wns.passpointConfiguration == null) { |
| return ewns.wns.wifiConfiguration.enterpriseConfig != null |
| && ewns.wns.wifiConfiguration.enterpriseConfig.isAuthenticationSimBased(); |
| } else { |
| return ewns.wns.passpointConfiguration.getCredential().getSimCredential() != null; |
| } |
| } |
| |
| private boolean checkNetworkSuggestionsNoNulls(List<WifiNetworkSuggestion> networkSuggestions) { |
| for (WifiNetworkSuggestion wns : networkSuggestions) { |
| if (wns == null || wns.wifiConfiguration == null) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private boolean validateNetworkSuggestions( |
| List<WifiNetworkSuggestion> networkSuggestions, String packageName, int uid) { |
| if (!checkNetworkSuggestionsNoNulls(networkSuggestions)) { |
| return false; |
| } |
| for (WifiNetworkSuggestion wns : networkSuggestions) { |
| if (wns.passpointConfiguration == null) { |
| WifiConfiguration config = wns.wifiConfiguration; |
| if (!WifiConfigurationUtil.validate(config, |
| WifiConfigurationUtil.VALIDATE_FOR_ADD)) { |
| return false; |
| } |
| if (config.isEnterprise()) { |
| final WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig; |
| if (enterpriseConfig.isEapMethodServerCertUsed() |
| && !enterpriseConfig.isMandatoryParameterSetForServerCertValidation()) { |
| Log.e(TAG, "Insecure enterprise suggestion is invalid."); |
| return false; |
| } |
| final String alias = enterpriseConfig.getClientKeyPairAliasInternal(); |
| if (alias != null && !mWifiKeyStore.validateKeyChainAlias(alias, uid)) { |
| Log.e(TAG, "Invalid client key pair KeyChain alias: " + alias); |
| return false; |
| } |
| } |
| |
| } else { |
| if (!wns.passpointConfiguration.validate()) { |
| return false; |
| } |
| } |
| if (!isAppWorkingAsCrossCarrierProvider(packageName) |
| && !isValidCarrierMergedNetworkSuggestion(wns)) { |
| Log.e(TAG, "Merged carrier network must be a metered, enterprise config with a " |
| + "valid subscription Id"); |
| return false; |
| } |
| if (!SdkLevel.isAtLeastS()) { |
| if (wns.wifiConfiguration.oemPaid) { |
| Log.e(TAG, "OEM paid suggestions are only allowed from Android S."); |
| return false; |
| } |
| if (wns.wifiConfiguration.oemPrivate) { |
| Log.e(TAG, "OEM private suggestions are only allowed from Android S."); |
| return false; |
| } |
| if (wns.wifiConfiguration.subscriptionId |
| != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { |
| Log.e(TAG, "Setting Subscription Id is only allowed from Android S."); |
| return false; |
| } |
| if (wns.priorityGroup != 0) { |
| Log.e(TAG, "Setting Priority group is only allowed from Android S."); |
| return false; |
| } |
| if (wns.wifiConfiguration.carrierMerged) { |
| Log.e(TAG, "Setting carrier merged network is only allowed from Android S."); |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| private boolean isValidCarrierMergedNetworkSuggestion(WifiNetworkSuggestion wns) { |
| if (!wns.wifiConfiguration.carrierMerged) { |
| // Not carrier merged. |
| return true; |
| } |
| if (!wns.wifiConfiguration.isEnterprise() && wns.passpointConfiguration == null) { |
| // Carrier merged network must be a enterprise network. |
| return false; |
| } |
| if (!WifiConfiguration.isMetered(wns.wifiConfiguration, null)) { |
| // Carrier merged network must be metered. |
| return false; |
| } |
| if (wns.wifiConfiguration.subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { |
| // Carrier merged network must have a valid subscription Id. |
| return false; |
| } |
| return true; |
| } |
| |
| private boolean validateCarrierNetworkSuggestions( |
| List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName, |
| int provisionerCarrierId) { |
| boolean isAppWorkingAsCrossCarrierProvider = isAppWorkingAsCrossCarrierProvider( |
| packageName); |
| boolean isCrossCarrierProvisioner = |
| mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(uid) |
| || isAppWorkingAsCrossCarrierProvider; |
| |
| for (WifiNetworkSuggestion suggestion : networkSuggestions) { |
| WifiConfiguration wifiConfiguration = suggestion.wifiConfiguration; |
| PasspointConfiguration passpointConfiguration = suggestion.passpointConfiguration; |
| if (wifiConfiguration.carrierMerged && !areCarrierMergedSuggestionsAllowed( |
| wifiConfiguration.subscriptionId, packageName)) { |
| // Carrier must be explicitly configured as merged carrier offload enabled |
| return false; |
| } |
| if (!isCrossCarrierProvisioner && provisionerCarrierId |
| == TelephonyManager.UNKNOWN_CARRIER_ID) { |
| // If an app doesn't have carrier privileges or carrier provisioning permission, |
| // suggests SIM-based network, sets CarrierId and sets SubscriptionId are illegal. |
| if (passpointConfiguration == null) { |
| if (wifiConfiguration.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) { |
| return false; |
| } |
| if (wifiConfiguration.subscriptionId |
| != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { |
| return false; |
| } |
| if (wifiConfiguration.enterpriseConfig != null |
| && wifiConfiguration.enterpriseConfig.isAuthenticationSimBased()) { |
| return false; |
| } |
| } else { |
| if (passpointConfiguration.getCarrierId() |
| != TelephonyManager.UNKNOWN_CARRIER_ID) { |
| return false; |
| } |
| if (passpointConfiguration.getSubscriptionId() |
| != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { |
| return false; |
| } |
| if (passpointConfiguration.getCredential() != null |
| && passpointConfiguration.getCredential().getSimCredential() != null) { |
| return false; |
| } |
| } |
| } else { |
| int carrierId = isCrossCarrierProvisioner ? wifiConfiguration.carrierId |
| : provisionerCarrierId; |
| int subId = passpointConfiguration == null ? wifiConfiguration.subscriptionId |
| : passpointConfiguration.getSubscriptionId(); |
| if (!mWifiCarrierInfoManager |
| .isSubIdMatchingCarrierId(subId, carrierId)) { |
| Log.e(TAG, "Subscription ID doesn't match the carrier. CarrierId:" |
| + carrierId + ", subscriptionId:" + subId + ", NetworkSuggestion:" |
| + suggestion); |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| private void stopTrackingAppOpsChange(@NonNull String packageName) { |
| AppOpsChangedListener appOpsChangedListener = |
| mAppOpsChangedListenerPerApp.remove(packageName); |
| if (appOpsChangedListener == null) { |
| Log.wtf(TAG, "No app ops listener found for " + packageName); |
| return; |
| } |
| mAppOps.stopWatchingMode(appOpsChangedListener); |
| } |
| |
| /** |
| * Remove provided list from that App active list. If provided list is empty, will remove all. |
| * Will disconnect network if current connected network is in the remove list. |
| */ |
| private void removeInternal( |
| @NonNull Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions, |
| @NonNull String packageName, |
| @NonNull PerAppInfo perAppInfo) { |
| // Get internal suggestions |
| Set<ExtendedWifiNetworkSuggestion> removingExtSuggestions = |
| new HashSet<>(perAppInfo.extNetworkSuggestions.values()); |
| if (!extNetworkSuggestions.isEmpty()) { |
| // Keep the internal suggestions need to remove. |
| removingExtSuggestions.retainAll(extNetworkSuggestions); |
| perAppInfo.extNetworkSuggestions.values().removeAll(extNetworkSuggestions); |
| } else { |
| // empty list is used to clear everything for the app. Store a copy for use below. |
| perAppInfo.extNetworkSuggestions.clear(); |
| } |
| if (perAppInfo.extNetworkSuggestions.isEmpty()) { |
| // Note: We don't remove the app entry even if there is no active suggestions because |
| // we want to keep the notification state for all apps that have ever provided |
| // suggestions. |
| if (mVerboseLoggingEnabled) Log.v(TAG, "No active suggestions for " + packageName); |
| // Stop tracking app-op changes from the app if they don't have active suggestions. |
| stopTrackingAppOpsChange(packageName); |
| } |
| // Clear the cache. |
| List<WifiNetworkSuggestion> removingSuggestions = new ArrayList<>(); |
| for (ExtendedWifiNetworkSuggestion ewns : removingExtSuggestions) { |
| if (ewns.wns.passpointConfiguration != null) { |
| // Clear the Passpoint config. |
| mWifiInjector.getPasspointManager().removeProvider( |
| ewns.perAppInfo.uid, |
| false, |
| ewns.wns.passpointConfiguration.getUniqueId(), null); |
| removeFromPassPointInfoMap(ewns); |
| } else { |
| if (ewns.wns.wifiConfiguration.isEnterprise()) { |
| mWifiKeyStore.removeKeys(ewns.wns.wifiConfiguration.enterpriseConfig); |
| } |
| removeFromScanResultMatchInfoMapAndRemoveRelatedScoreCard(ewns); |
| mWifiConfigManager.removeConnectChoiceFromAllNetworks(ewns |
| .createInternalWifiConfiguration(mWifiCarrierInfoManager) |
| .getProfileKey()); |
| } |
| removingSuggestions.add(ewns.wns); |
| // Remove the config from WifiConfigManager. If current connected suggestion is remove, |
| // would trigger a disconnect. |
| mWifiConfigManager.removeSuggestionConfiguredNetwork( |
| ewns.createInternalWifiConfiguration(mWifiCarrierInfoManager)); |
| } |
| for (OnSuggestionUpdateListener listener : mListeners) { |
| listener.onSuggestionsRemoved(removingSuggestions); |
| } |
| } |
| |
| /** |
| * Remove the provided list of network suggestions from the corresponding app's active list. |
| */ |
| public @WifiManager.NetworkSuggestionsStatusCode int remove( |
| List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName) { |
| if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(uid)) { |
| Log.e(TAG, "UID " + uid + " not visible to the current user"); |
| return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL; |
| } |
| if (!mUserDataLoaded) { |
| Log.e(TAG, "Remove Network suggestion before boot complete is not allowed."); |
| return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL; |
| } |
| if (networkSuggestions == null) { |
| Log.w(TAG, "Null list of network suggestions for " + packageName + ". Ignoring"); |
| return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; |
| } |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "Removing " + networkSuggestions.size() + " networks from " + packageName); |
| } |
| if (!checkNetworkSuggestionsNoNulls(networkSuggestions)) { |
| Log.e(TAG, "Null in suggestion remove from app: " + packageName); |
| return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID; |
| } |
| PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName); |
| if (perAppInfo == null) { |
| Log.e(TAG, "Failed to remove network suggestions for " + packageName |
| + ". No network suggestions found"); |
| return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID; |
| } |
| Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = |
| convertToExtendedWnsSet(networkSuggestions, perAppInfo); |
| Set<Integer> keySet = extNetworkSuggestions |
| .stream() |
| .map(ExtendedWifiNetworkSuggestion::hashCode) |
| .collect(Collectors.toSet()); |
| // check if all the request network suggestions are present in the active list. |
| if (!extNetworkSuggestions.isEmpty() |
| && !perAppInfo.extNetworkSuggestions.keySet().containsAll(keySet)) { |
| Log.e(TAG, "Failed to remove network suggestions for " + packageName |
| + ". Network suggestions not found in active network suggestions"); |
| return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID; |
| } |
| removeInternal(extNetworkSuggestions, packageName, perAppInfo); |
| saveToStore(); |
| mWifiMetrics.incrementNetworkSuggestionApiNumModification(); |
| mWifiMetrics.noteNetworkSuggestionApiListSizeHistogram(getAllMaxSizes()); |
| return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; |
| } |
| |
| /** |
| * Remove all tracking of the app that has been uninstalled. |
| */ |
| public void removeApp(@NonNull String packageName) { |
| PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName); |
| if (perAppInfo == null) return; |
| removeInternal(List.of(), packageName, perAppInfo); |
| // Remove the package fully from the internal database. |
| mActiveNetworkSuggestionsPerApp.remove(packageName); |
| RemoteCallbackList<ISuggestionConnectionStatusListener> listenerTracker = |
| mSuggestionStatusListenerPerApp.remove(packageName); |
| if (listenerTracker != null) listenerTracker.kill(); |
| saveToStore(); |
| Log.i(TAG, "Removed " + packageName); |
| } |
| |
| /** |
| * Get all network suggestion for target App |
| * @return List of WifiNetworkSuggestions |
| */ |
| public @NonNull List<WifiNetworkSuggestion> get(@NonNull String packageName, int uid) { |
| List<WifiNetworkSuggestion> networkSuggestionList = new ArrayList<>(); |
| if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(uid)) { |
| Log.e(TAG, "UID " + uid + " not visible to the current user"); |
| return networkSuggestionList; |
| } |
| if (!mUserDataLoaded) { |
| Log.e(TAG, "Get Network suggestion before boot complete is not allowed."); |
| return networkSuggestionList; |
| } |
| PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName); |
| // if App never suggested return empty list. |
| if (perAppInfo == null) return networkSuggestionList; |
| for (ExtendedWifiNetworkSuggestion extendedSuggestion : perAppInfo.extNetworkSuggestions |
| .values()) { |
| networkSuggestionList.add(extendedSuggestion.wns); |
| } |
| return networkSuggestionList; |
| } |
| |
| /** |
| * Clear all internal state (for network settings reset). |
| */ |
| public void clear() { |
| Iterator<Map.Entry<String, PerAppInfo>> iter = |
| mActiveNetworkSuggestionsPerApp.entrySet().iterator(); |
| while (iter.hasNext()) { |
| Map.Entry<String, PerAppInfo> entry = iter.next(); |
| removeInternal(List.of(), entry.getKey(), entry.getValue()); |
| iter.remove(); |
| } |
| mSuggestionStatusListenerPerApp.clear(); |
| mSuggestionUserApprovalStatusListenerPerApp.clear(); |
| resetNotification(); |
| saveToStore(); |
| Log.i(TAG, "Cleared all internal state"); |
| } |
| |
| /** |
| * Check if network suggestions are enabled or disabled for the app. |
| */ |
| public boolean hasUserApprovedForApp(String packageName) { |
| PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName); |
| if (perAppInfo == null) return false; |
| |
| return perAppInfo.hasUserApproved; |
| } |
| |
| /** |
| * Enable or Disable network suggestions for the app. |
| */ |
| public void setHasUserApprovedForApp(boolean approved, int uid, String packageName) { |
| PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName); |
| if (perAppInfo == null) return; |
| |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "Setting the app " + packageName |
| + (approved ? " approved" : " not approved")); |
| } |
| perAppInfo.hasUserApproved = approved; |
| onSuggestionUserApprovalStatusChanged(uid, packageName); |
| saveToStore(); |
| } |
| |
| /** |
| * When user approve the IMSI protection exemption for carrier, restore the initial auto join |
| * configure. If user already change it to enabled, keep that choice. |
| */ |
| private void restoreInitialAutojoinForCarrierId(int carrierId) { |
| for (PerAppInfo appInfo : mActiveNetworkSuggestionsPerApp.values()) { |
| for (ExtendedWifiNetworkSuggestion ewns : appInfo.extNetworkSuggestions.values()) { |
| if (!(isSimBasedSuggestion(ewns) |
| && getCarrierIdFromSuggestion(ewns) == carrierId)) { |
| continue; |
| } |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "Restore auto-join for suggestion: " + ewns); |
| } |
| ewns.isAutojoinEnabled |= ewns.wns.isInitialAutoJoinEnabled; |
| // Restore passpoint provider auto join. |
| if (ewns.wns.passpointConfiguration != null) { |
| mWifiInjector.getPasspointManager() |
| .enableAutojoin(ewns.wns.passpointConfiguration.getUniqueId(), |
| null, ewns.isAutojoinEnabled); |
| } else { |
| // Update WifiConfigManager to sync auto-join. |
| updateWifiConfigInWcmIfPresent(ewns.createInternalWifiConfiguration( |
| mWifiCarrierInfoManager), |
| ewns.perAppInfo.uid, ewns.perAppInfo.packageName); |
| } |
| } |
| } |
| saveToStore(); |
| } |
| |
| /** |
| * Returns a set of all network suggestions across all apps. |
| */ |
| @VisibleForTesting |
| public Set<WifiNetworkSuggestion> getAllNetworkSuggestions() { |
| return mActiveNetworkSuggestionsPerApp.values() |
| .stream() |
| .flatMap(e -> convertToWnsSet(e.extNetworkSuggestions.values()) |
| .stream()) |
| .collect(Collectors.toSet()); |
| } |
| |
| /** |
| * Returns a set of all network suggestions across all apps that have been approved by user. |
| */ |
| public Set<WifiNetworkSuggestion> getAllApprovedNetworkSuggestions() { |
| final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage(); |
| return mActiveNetworkSuggestionsPerApp.values() |
| .stream() |
| .filter(e -> e.isApproved(activeScorerPackage)) |
| .flatMap(e -> convertToWnsSet(e.extNetworkSuggestions.values()) |
| .stream()) |
| .collect(Collectors.toSet()); |
| } |
| |
| /** |
| * Get all user approved, non-passpoint networks from suggestion. |
| */ |
| public List<WifiConfiguration> getAllScanOptimizationSuggestionNetworks() { |
| List<WifiConfiguration> networks = new ArrayList<>(); |
| final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage(); |
| for (PerAppInfo info : mActiveNetworkSuggestionsPerApp.values()) { |
| if (!info.isApproved(activeScorerPackage)) { |
| continue; |
| } |
| for (ExtendedWifiNetworkSuggestion ewns : info.extNetworkSuggestions.values()) { |
| if (ewns.wns.getPasspointConfig() != null) { |
| continue; |
| } |
| WifiConfiguration network = mWifiConfigManager |
| .getConfiguredNetwork(ewns.wns.getWifiConfiguration() |
| .getProfileKey()); |
| if (network == null) { |
| network = ewns.createInternalWifiConfiguration(mWifiCarrierInfoManager); |
| } |
| networks.add(network); |
| } |
| } |
| return networks; |
| } |
| |
| private List<Integer> getAllMaxSizes() { |
| return mActiveNetworkSuggestionsPerApp.values() |
| .stream() |
| .map(e -> e.maxSize) |
| .collect(Collectors.toList()); |
| } |
| |
| private PendingIntent getPrivateBroadcast(@NonNull String action, |
| @NonNull Pair<String, String> extra1, @NonNull Pair<String, Integer> extra2) { |
| Intent intent = new Intent(action) |
| .setPackage(mContext.getServiceWifiPackageName()) |
| .putExtra(extra1.first, extra1.second) |
| .putExtra(extra2.first, extra2.second); |
| return mFrameworkFacade.getBroadcast(mContext, 0, intent, |
| PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); |
| } |
| |
| private @NonNull CharSequence getAppName(@NonNull String packageName, int uid) { |
| ApplicationInfo applicationInfo = null; |
| try { |
| applicationInfo = mContext.getPackageManager().getApplicationInfoAsUser( |
| packageName, 0, UserHandle.getUserHandleForUid(uid)); |
| } catch (PackageManager.NameNotFoundException e) { |
| Log.e(TAG, "Failed to find app name for " + packageName); |
| return ""; |
| } |
| CharSequence appName = mPackageManager.getApplicationLabel(applicationInfo); |
| return (appName != null) ? appName : ""; |
| } |
| |
| /** |
| * Check if the request came from foreground app. |
| */ |
| private boolean isSuggestionFromForegroundApp(@NonNull String packageName) { |
| try { |
| return mActivityManager.getPackageImportance(packageName) |
| <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; |
| } catch (SecurityException e) { |
| Log.e(TAG, "Failed to check the app state", e); |
| return false; |
| } |
| } |
| |
| private void sendUserApprovalDialog(@NonNull String packageName, int uid) { |
| CharSequence appName = getAppName(packageName, uid); |
| AlertDialog dialog = mFrameworkFacade.makeAlertDialogBuilder(mContext) |
| .setTitle(mResources.getString(R.string.wifi_suggestion_title)) |
| .setMessage(mResources.getString(R.string.wifi_suggestion_content, appName)) |
| .setPositiveButton( |
| mResources.getText(R.string.wifi_suggestion_action_allow_app), |
| (d, which) -> mHandler.post( |
| () -> handleUserAllowAction(uid, packageName))) |
| .setNegativeButton( |
| mResources.getText(R.string.wifi_suggestion_action_disallow_app), |
| (d, which) -> mHandler.post( |
| () -> handleUserDisallowAction(uid, packageName))) |
| .setOnDismissListener( |
| (d) -> mHandler.post(() -> handleUserDismissAction())) |
| .setOnCancelListener( |
| (d) -> mHandler.post(() -> handleUserDismissAction())) |
| .create(); |
| dialog.setCanceledOnTouchOutside(false); |
| dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); |
| dialog.getWindow().addSystemFlags( |
| WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS); |
| dialog.show(); |
| mIsLastUserApprovalUiDialog = true; |
| } |
| |
| private void sendUserApprovalNotification(@NonNull String packageName, int uid) { |
| Notification.Action userAllowAppNotificationAction = |
| new Notification.Action.Builder(null, |
| mResources.getText(R.string.wifi_suggestion_action_allow_app), |
| getPrivateBroadcast(NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION, |
| Pair.create(EXTRA_PACKAGE_NAME, packageName), |
| Pair.create(EXTRA_UID, uid))) |
| .build(); |
| Notification.Action userDisallowAppNotificationAction = |
| new Notification.Action.Builder(null, |
| mResources.getText(R.string.wifi_suggestion_action_disallow_app), |
| getPrivateBroadcast(NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION, |
| Pair.create(EXTRA_PACKAGE_NAME, packageName), |
| Pair.create(EXTRA_UID, uid))) |
| .build(); |
| |
| CharSequence appName = getAppName(packageName, uid); |
| Notification notification = mFrameworkFacade.makeNotificationBuilder( |
| mContext, WifiService.NOTIFICATION_NETWORK_STATUS) |
| .setSmallIcon(Icon.createWithResource(mContext.getWifiOverlayApkPkgName(), |
| com.android.wifi.resources.R.drawable.stat_notify_wifi_in_range)) |
| .setTicker(mResources.getString(R.string.wifi_suggestion_title)) |
| .setContentTitle(mResources.getString(R.string.wifi_suggestion_title)) |
| .setStyle(new Notification.BigTextStyle() |
| .bigText(mResources.getString(R.string.wifi_suggestion_content, appName))) |
| .setDeleteIntent(getPrivateBroadcast(NOTIFICATION_USER_DISMISSED_INTENT_ACTION, |
| Pair.create(EXTRA_PACKAGE_NAME, packageName), Pair.create(EXTRA_UID, uid))) |
| .setShowWhen(false) |
| .setLocalOnly(true) |
| .setColor(mResources.getColor(android.R.color.system_notification_accent_color, |
| mContext.getTheme())) |
| .addAction(userAllowAppNotificationAction) |
| .addAction(userDisallowAppNotificationAction) |
| .setTimeoutAfter(NOTIFICATION_EXPIRY_MILLS) |
| .build(); |
| |
| // Post the notification. |
| mNotificationManager.notify(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE, notification); |
| mNotificationUpdateTime = mClock.getElapsedSinceBootMillis() |
| + NOTIFICATION_UPDATE_DELAY_MILLS; |
| mIsLastUserApprovalUiDialog = false; |
| } |
| |
| /** |
| * Send user approval notification if the app is not approved |
| * @param packageName app package name |
| * @param uid app UID |
| * @return true if app is not approved and send notification. |
| */ |
| private boolean sendUserApprovalNotificationIfNotApproved( |
| @NonNull String packageName, @NonNull int uid) { |
| if (!mActiveNetworkSuggestionsPerApp.containsKey(packageName)) { |
| Log.wtf(TAG, "AppInfo is missing for " + packageName); |
| return false; |
| } |
| if (mActiveNetworkSuggestionsPerApp.get(packageName).hasUserApproved) { |
| return false; // already approved. |
| } |
| |
| if (mNotificationUpdateTime > mClock.getElapsedSinceBootMillis()) { |
| return false; // Active notification is still available, do not update. |
| } |
| Log.i(TAG, "Sending user approval notification for " + packageName); |
| sendUserApprovalNotification(packageName, uid); |
| return true; |
| } |
| |
| private @Nullable Set<ExtendedWifiNetworkSuggestion> |
| getNetworkSuggestionsForScanResultMatchInfo( |
| @NonNull ScanResultMatchInfo scanResultMatchInfo, @Nullable MacAddress bssid) { |
| Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = new HashSet<>(); |
| if (bssid != null) { |
| Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsWithBssid = |
| mActiveScanResultMatchInfoWithBssid.get( |
| Pair.create(scanResultMatchInfo, bssid)); |
| if (matchingExtNetworkSuggestionsWithBssid != null) { |
| extNetworkSuggestions.addAll(matchingExtNetworkSuggestionsWithBssid); |
| } |
| } |
| Set<ExtendedWifiNetworkSuggestion> matchingNetworkSuggestionsWithNoBssid = |
| mActiveScanResultMatchInfoWithNoBssid.get(scanResultMatchInfo); |
| if (matchingNetworkSuggestionsWithNoBssid != null) { |
| extNetworkSuggestions.addAll(matchingNetworkSuggestionsWithNoBssid); |
| } |
| if (extNetworkSuggestions.isEmpty()) { |
| return null; |
| } |
| return extNetworkSuggestions; |
| } |
| |
| private @Nullable Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForFqdnMatch( |
| @Nullable String fqdn) { |
| if (TextUtils.isEmpty(fqdn)) { |
| return null; |
| } |
| return mPasspointInfo.get(fqdn); |
| } |
| |
| /** |
| * Returns a set of all network suggestions matching the provided FQDN. |
| */ |
| public @NonNull Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForFqdn(String fqdn) { |
| Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = |
| getNetworkSuggestionsForFqdnMatch(fqdn); |
| if (extNetworkSuggestions == null) { |
| return Set.of(); |
| } |
| final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage(); |
| Set<ExtendedWifiNetworkSuggestion> approvedExtNetworkSuggestions = new HashSet<>(); |
| for (ExtendedWifiNetworkSuggestion ewns : extNetworkSuggestions) { |
| if (!ewns.perAppInfo.isApproved(activeScorerPackage)) { |
| sendUserApprovalNotificationIfNotApproved(ewns.perAppInfo.packageName, |
| ewns.perAppInfo.uid); |
| continue; |
| } |
| if (ewns.wns.wifiConfiguration.carrierMerged && !areCarrierMergedSuggestionsAllowed( |
| ewns.wns.wifiConfiguration.subscriptionId, ewns.perAppInfo.packageName)) { |
| continue; |
| } |
| if (isSimBasedSuggestion(ewns)) { |
| mWifiCarrierInfoManager.sendImsiProtectionExemptionNotificationIfRequired( |
| getCarrierIdFromSuggestion(ewns)); |
| } |
| approvedExtNetworkSuggestions.add(ewns); |
| } |
| |
| if (approvedExtNetworkSuggestions.isEmpty()) { |
| return Set.of(); |
| } |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "getNetworkSuggestionsForFqdn Found " |
| + approvedExtNetworkSuggestions + " for " + fqdn); |
| } |
| return approvedExtNetworkSuggestions; |
| } |
| |
| /** |
| * Returns a set of all network suggestions matching the provided scan detail. |
| */ |
| public @NonNull Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForScanDetail( |
| @NonNull ScanDetail scanDetail) { |
| ScanResult scanResult = scanDetail.getScanResult(); |
| if (scanResult == null) { |
| Log.e(TAG, "No scan result found in scan detail"); |
| return Set.of(); |
| } |
| Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = null; |
| try { |
| ScanResultMatchInfo scanResultMatchInfo = |
| ScanResultMatchInfo.fromScanResult(scanResult); |
| extNetworkSuggestions = getNetworkSuggestionsForScanResultMatchInfo( |
| scanResultMatchInfo, MacAddress.fromString(scanResult.BSSID)); |
| } catch (IllegalArgumentException e) { |
| Log.e(TAG, "Failed to lookup network from scan result match info map", e); |
| } |
| if (extNetworkSuggestions == null) { |
| return Set.of(); |
| } |
| final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage(); |
| Set<ExtendedWifiNetworkSuggestion> approvedExtNetworkSuggestions = new HashSet<>(); |
| for (ExtendedWifiNetworkSuggestion ewns : extNetworkSuggestions) { |
| if (!ewns.perAppInfo.isApproved(activeScorerPackage)) { |
| sendUserApprovalNotificationIfNotApproved(ewns.perAppInfo.packageName, |
| ewns.perAppInfo.uid); |
| continue; |
| } |
| if (ewns.wns.wifiConfiguration.carrierMerged && !areCarrierMergedSuggestionsAllowed( |
| ewns.wns.wifiConfiguration.subscriptionId, ewns.perAppInfo.packageName)) { |
| continue; |
| } |
| if (isSimBasedSuggestion(ewns)) { |
| mWifiCarrierInfoManager.sendImsiProtectionExemptionNotificationIfRequired( |
| getCarrierIdFromSuggestion(ewns)); |
| } |
| approvedExtNetworkSuggestions.add(ewns); |
| } |
| |
| if (approvedExtNetworkSuggestions.isEmpty()) { |
| return Set.of(); |
| } |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "getNetworkSuggestionsForScanDetail Found " |
| + approvedExtNetworkSuggestions + " for " + scanResult.SSID |
| + "[" + scanResult.capabilities + "]"); |
| } |
| return approvedExtNetworkSuggestions; |
| } |
| |
| /** |
| * Returns a set of all network suggestions matching the provided the WifiConfiguration. |
| */ |
| public @Nullable Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForWifiConfiguration( |
| @NonNull WifiConfiguration wifiConfiguration, @Nullable String bssid) { |
| Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = null; |
| if (wifiConfiguration.isPasspoint()) { |
| extNetworkSuggestions = getNetworkSuggestionsForFqdnMatch(wifiConfiguration.FQDN); |
| } else { |
| try { |
| ScanResultMatchInfo scanResultMatchInfo = |
| ScanResultMatchInfo.fromWifiConfiguration(wifiConfiguration); |
| extNetworkSuggestions = getNetworkSuggestionsForScanResultMatchInfo( |
| scanResultMatchInfo, bssid == null ? null : MacAddress.fromString(bssid)); |
| } catch (IllegalArgumentException e) { |
| Log.e(TAG, "Failed to lookup network from scan result match info map", e); |
| } |
| } |
| if (extNetworkSuggestions == null || extNetworkSuggestions.isEmpty()) { |
| return null; |
| } |
| final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage(); |
| Set<ExtendedWifiNetworkSuggestion> approvedExtNetworkSuggestions = |
| extNetworkSuggestions |
| .stream() |
| .filter(n -> n.perAppInfo.isApproved(activeScorerPackage)) |
| .collect(Collectors.toSet()); |
| if (approvedExtNetworkSuggestions.isEmpty()) { |
| return null; |
| } |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "getNetworkSuggestionsForWifiConfiguration Found " |
| + approvedExtNetworkSuggestions + " for " + wifiConfiguration.SSID |
| + wifiConfiguration.FQDN + "[" + wifiConfiguration.allowedKeyManagement + "]"); |
| } |
| return approvedExtNetworkSuggestions; |
| } |
| |
| /** |
| * Retrieve the WifiConfigurations for all matched suggestions which allow user manually connect |
| * and user already approved for non-open networks. |
| */ |
| public @NonNull List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser( |
| List<ScanResult> scanResults) { |
| // Create a temporary look-up table. |
| // As they are all single type configurations, they should have unique keys. |
| Map<String, WifiConfiguration> wifiConfigMap = new HashMap<>(); |
| WifiConfigurationUtil.convertMultiTypeConfigsToLegacyConfigs( |
| mWifiConfigManager.getConfiguredNetworks()) |
| .forEach(c -> wifiConfigMap.put(c.getProfileKey(), c)); |
| |
| // Create a HashSet to avoid return multiple result for duplicate ScanResult. |
| Set<String> networkKeys = new HashSet<>(); |
| List<WifiConfiguration> sharedWifiConfigs = new ArrayList<>(); |
| for (ScanResult scanResult : scanResults) { |
| ScanResultMatchInfo scanResultMatchInfo = |
| ScanResultMatchInfo.fromScanResult(scanResult); |
| if (scanResultMatchInfo.securityParamsList.size() == 0) continue; |
| // Only filter legacy Open network. |
| if (scanResultMatchInfo.securityParamsList.size() == 1 |
| && scanResultMatchInfo.getDefaultSecurityParams().getSecurityType() |
| == WifiConfiguration.SECURITY_TYPE_OPEN) { |
| continue; |
| } |
| Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = |
| getNetworkSuggestionsForScanResultMatchInfo( |
| scanResultMatchInfo, MacAddress.fromString(scanResult.BSSID)); |
| if (extNetworkSuggestions == null || extNetworkSuggestions.isEmpty()) { |
| continue; |
| } |
| Set<ExtendedWifiNetworkSuggestion> sharedNetworkSuggestions = extNetworkSuggestions |
| .stream() |
| .filter(ewns -> ewns.perAppInfo.hasUserApproved |
| && ewns.wns.isUserAllowedToManuallyConnect) |
| .collect(Collectors.toSet()); |
| if (sharedNetworkSuggestions.isEmpty()) { |
| continue; |
| } |
| for (ExtendedWifiNetworkSuggestion ewns : sharedNetworkSuggestions) { |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "getWifiConfigForMatchedNetworkSuggestionsSharedWithUser Found " |
| + ewns + " for " + scanResult.SSID |
| + "[" + scanResult.capabilities + "]"); |
| } |
| WifiConfiguration config = ewns.createInternalWifiConfiguration( |
| mWifiCarrierInfoManager); |
| if (config.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID |
| && !mWifiCarrierInfoManager.isSimReady(config.subscriptionId)) { |
| continue; |
| } |
| if (config.carrierMerged && !areCarrierMergedSuggestionsAllowed( |
| config.subscriptionId, ewns.perAppInfo.packageName)) { |
| continue; |
| } |
| WifiConfiguration wCmWifiConfig = wifiConfigMap.get(config.getProfileKey()); |
| if (wCmWifiConfig == null) { |
| continue; |
| } |
| if (networkKeys.add(wCmWifiConfig.getProfileKey())) { |
| sharedWifiConfigs.add(wCmWifiConfig); |
| } |
| } |
| } |
| return sharedWifiConfigs; |
| } |
| |
| /** |
| * Check if the given passpoint suggestion has user approval and allow user manually connect. |
| */ |
| public boolean isPasspointSuggestionSharedWithUser(WifiConfiguration config) { |
| if (WifiConfiguration.isMetered(config, null) |
| && mWifiCarrierInfoManager.isCarrierNetworkFromNonDefaultDataSim(config)) { |
| return false; |
| } |
| if (config.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) { |
| int subId = mWifiCarrierInfoManager.getBestMatchSubscriptionId(config); |
| if (!mWifiCarrierInfoManager.isSimReady(subId)) { |
| return false; |
| } |
| } |
| Set<ExtendedWifiNetworkSuggestion> extendedWifiNetworkSuggestions = |
| getNetworkSuggestionsForFqdnMatch(config.FQDN); |
| Set<ExtendedWifiNetworkSuggestion> matchedSuggestions = |
| extendedWifiNetworkSuggestions == null ? null : extendedWifiNetworkSuggestions |
| .stream().filter(ewns -> ewns.perAppInfo.uid == config.creatorUid) |
| .collect(Collectors.toSet()); |
| if (matchedSuggestions == null || matchedSuggestions.isEmpty()) { |
| Log.e(TAG, "Matched network suggestion is missing for FQDN:" + config.FQDN); |
| return false; |
| } |
| ExtendedWifiNetworkSuggestion suggestion = matchedSuggestions |
| .stream().findAny().get(); |
| return suggestion.wns.isUserAllowedToManuallyConnect |
| && suggestion.perAppInfo.hasUserApproved; |
| } |
| |
| /** |
| * Get hidden network from active network suggestions. |
| * Todo(): Now limit by a fixed number, maybe we can try rotation? |
| * @return set of WifiConfigurations |
| */ |
| public List<WifiScanner.ScanSettings.HiddenNetwork> retrieveHiddenNetworkList() { |
| List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworks = new ArrayList<>(); |
| for (PerAppInfo appInfo : mActiveNetworkSuggestionsPerApp.values()) { |
| if (!appInfo.hasUserApproved) continue; |
| for (ExtendedWifiNetworkSuggestion ewns : appInfo.extNetworkSuggestions.values()) { |
| if (!ewns.wns.wifiConfiguration.hiddenSSID) continue; |
| hiddenNetworks.add( |
| new WifiScanner.ScanSettings.HiddenNetwork( |
| ewns.wns.wifiConfiguration.SSID)); |
| if (hiddenNetworks.size() >= NUMBER_OF_HIDDEN_NETWORK_FOR_ONE_SCAN) { |
| return hiddenNetworks; |
| } |
| } |
| } |
| return hiddenNetworks; |
| } |
| |
| /** |
| * Helper method to send the post connection broadcast to specified package. |
| */ |
| private void sendPostConnectionBroadcast( |
| ExtendedWifiNetworkSuggestion extSuggestion) { |
| Intent intent = new Intent(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION); |
| intent.putExtra(WifiManager.EXTRA_NETWORK_SUGGESTION, extSuggestion.wns); |
| // Intended to wakeup the receiving app so set the specific package name. |
| intent.setPackage(extSuggestion.perAppInfo.packageName); |
| mContext.sendBroadcastAsUser( |
| intent, UserHandle.getUserHandleForUid(extSuggestion.perAppInfo.uid)); |
| } |
| |
| /** |
| * Helper method to send the post connection broadcast to specified package. |
| */ |
| private void sendPostConnectionBroadcastIfAllowed( |
| ExtendedWifiNetworkSuggestion matchingExtSuggestion, @NonNull String message) { |
| try { |
| mWifiPermissionsUtil.enforceCanAccessScanResults( |
| matchingExtSuggestion.perAppInfo.packageName, |
| matchingExtSuggestion.perAppInfo.featureId, |
| matchingExtSuggestion.perAppInfo.uid, message); |
| } catch (SecurityException se) { |
| Log.w(TAG, "Permission denied for sending post connection broadcast to " |
| + matchingExtSuggestion.perAppInfo.packageName); |
| return; |
| } |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "Sending post connection broadcast to " |
| + matchingExtSuggestion.perAppInfo.packageName); |
| } |
| sendPostConnectionBroadcast(matchingExtSuggestion); |
| } |
| |
| /** |
| * Send out the {@link WifiManager#ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} to the |
| * network suggestion that provided credential for the current connection network. |
| * If current connection network is open user saved network, broadcast will be only sent out to |
| * one of the carrier apps that suggested matched network suggestions. |
| * |
| * @param connectedNetwork {@link WifiConfiguration} representing the network connected to. |
| * @param connectedBssid BSSID of the network connected to. |
| */ |
| private void handleConnectionSuccess( |
| @NonNull WifiConfiguration connectedNetwork, @NonNull String connectedBssid) { |
| if (!(connectedNetwork.fromWifiNetworkSuggestion || connectedNetwork.isOpenNetwork())) { |
| return; |
| } |
| Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions = |
| getNetworkSuggestionsForWifiConfiguration(connectedNetwork, connectedBssid); |
| |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "Network suggestions matching the connection " |
| + matchingExtNetworkSuggestions); |
| } |
| if (matchingExtNetworkSuggestions == null |
| || matchingExtNetworkSuggestions.isEmpty()) return; |
| |
| Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsFromTargetApp; |
| if (connectedNetwork.fromWifiNetworkSuggestion) { |
| matchingExtNetworkSuggestionsFromTargetApp = |
| getMatchedSuggestionsWithSameProfileKey(matchingExtNetworkSuggestions, |
| connectedNetwork); |
| if (matchingExtNetworkSuggestionsFromTargetApp.isEmpty()) { |
| Log.wtf(TAG, "Current connected network suggestion is missing!"); |
| return; |
| } |
| } else { |
| // If not suggestion, the connected network is open network. |
| // For saved open network, found the matching suggestion from carrier privileged |
| // apps. As we only expect one suggestor app to take action on post connection, if |
| // multiple apps suggested matched suggestions, framework will randomly pick one. |
| matchingExtNetworkSuggestionsFromTargetApp = matchingExtNetworkSuggestions.stream() |
| .filter(x -> x.perAppInfo.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID |
| || mWifiPermissionsUtil |
| .checkNetworkCarrierProvisioningPermission(x.perAppInfo.uid)) |
| .limit(1).collect(Collectors.toSet()); |
| if (matchingExtNetworkSuggestionsFromTargetApp.isEmpty()) { |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "No suggestion matched connected user saved open network."); |
| } |
| return; |
| } |
| } |
| |
| mWifiMetrics.incrementNetworkSuggestionApiNumConnectSuccess(); |
| // Find subset of network suggestions have set |isAppInteractionRequired|. |
| Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsWithReqAppInteraction = |
| matchingExtNetworkSuggestionsFromTargetApp.stream() |
| .filter(x -> x.wns.isAppInteractionRequired) |
| .collect(Collectors.toSet()); |
| if (matchingExtNetworkSuggestionsWithReqAppInteraction.isEmpty()) return; |
| |
| // Iterate over the matching network suggestions list: |
| // a) Ensure that these apps have the necessary location permissions. |
| // b) Send directed broadcast to the app with their corresponding network suggestion. |
| for (ExtendedWifiNetworkSuggestion matchingExtNetworkSuggestion |
| : matchingExtNetworkSuggestionsWithReqAppInteraction) { |
| sendPostConnectionBroadcastIfAllowed( |
| matchingExtNetworkSuggestion, |
| "Connected to " + matchingExtNetworkSuggestion.wns.wifiConfiguration.SSID |
| + ". featureId is first feature of the app using network suggestions"); |
| } |
| } |
| |
| /** |
| * Handle connection failure. |
| * |
| * @param network {@link WifiConfiguration} representing the network that connection failed to. |
| * @param bssid BSSID of the network connection failed to if known, else null. |
| * @param failureCode failure reason code. |
| */ |
| private void handleConnectionFailure(@NonNull WifiConfiguration network, |
| @Nullable String bssid, int failureCode) { |
| if (!network.fromWifiNetworkSuggestion) { |
| return; |
| } |
| Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions = |
| getNetworkSuggestionsForWifiConfiguration(network, bssid); |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "Network suggestions matching the connection failure " |
| + matchingExtNetworkSuggestions); |
| } |
| if (matchingExtNetworkSuggestions == null |
| || matchingExtNetworkSuggestions.isEmpty()) return; |
| |
| mWifiMetrics.incrementNetworkSuggestionApiNumConnectFailure(); |
| // TODO (b/115504887, b/112196799): Blocklist the corresponding network suggestion if |
| // the connection failed. |
| |
| // Find subset of network suggestions which suggested the connection failure network. |
| Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsFromTargetApp = |
| getMatchedSuggestionsWithSameProfileKey(matchingExtNetworkSuggestions, network); |
| if (matchingExtNetworkSuggestionsFromTargetApp.isEmpty()) { |
| Log.wtf(TAG, "Current connection failure network suggestion is missing!"); |
| return; |
| } |
| |
| for (ExtendedWifiNetworkSuggestion matchingExtNetworkSuggestion |
| : matchingExtNetworkSuggestionsFromTargetApp) { |
| sendConnectionFailureIfAllowed(matchingExtNetworkSuggestion.perAppInfo.packageName, |
| matchingExtNetworkSuggestion.perAppInfo.featureId, |
| matchingExtNetworkSuggestion.perAppInfo.uid, |
| matchingExtNetworkSuggestion.wns, failureCode); |
| } |
| } |
| |
| private Set<ExtendedWifiNetworkSuggestion> getMatchedSuggestionsWithSameProfileKey( |
| Set<ExtendedWifiNetworkSuggestion> matchingSuggestions, WifiConfiguration network) { |
| if (matchingSuggestions == null || matchingSuggestions.isEmpty()) { |
| return Set.of(); |
| } |
| Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsWithSameProfileKey = |
| new HashSet<>(); |
| for (ExtendedWifiNetworkSuggestion ewns : matchingSuggestions) { |
| WifiConfiguration config = ewns |
| .createInternalWifiConfiguration(mWifiCarrierInfoManager); |
| if (config.getProfileKey().equals(network.getProfileKey()) |
| && config.creatorName.equals(network.creatorName)) { |
| matchingExtNetworkSuggestionsWithSameProfileKey.add(ewns); |
| } |
| } |
| return matchingExtNetworkSuggestionsWithSameProfileKey; |
| } |
| |
| /** |
| * Invoked by {@link ClientModeImpl} on end of connection attempt to a network. |
| * |
| * @param failureCode Failure codes representing {@link WifiMetrics.ConnectionEvent} codes. |
| * @param network WifiConfiguration corresponding to the current network. |
| * @param bssid BSSID of the current network. |
| */ |
| public void handleConnectionAttemptEnded( |
| int failureCode, @NonNull WifiConfiguration network, @Nullable String bssid) { |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "handleConnectionAttemptEnded " + failureCode + ", " + network); |
| } |
| if (failureCode == WifiMetrics.ConnectionEvent.FAILURE_NONE) { |
| handleConnectionSuccess(network, bssid); |
| } else { |
| handleConnectionFailure(network, bssid, failureCode); |
| } |
| } |
| |
| /** |
| * Send network connection failure event to app when an connection attempt failure. |
| * @param packageName package name to send event |
| * @param featureId The feature in the package |
| * @param uid uid of the app. |
| * @param matchingSuggestion suggestion on this connection failure |
| * @param connectionEvent connection failure code |
| */ |
| private void sendConnectionFailureIfAllowed(String packageName, @Nullable String featureId, |
| int uid, @NonNull WifiNetworkSuggestion matchingSuggestion, int connectionEvent) { |
| RemoteCallbackList<ISuggestionConnectionStatusListener> listenersTracker = |
| mSuggestionStatusListenerPerApp.get(packageName); |
| if (listenersTracker == null || listenersTracker.getRegisteredCallbackCount() == 0) { |
| return; |
| } |
| try { |
| mWifiPermissionsUtil.enforceCanAccessScanResults( |
| packageName, featureId, uid, "Connection failure"); |
| } catch (SecurityException se) { |
| Log.w(TAG, "Permission denied for sending connection failure event to " + packageName); |
| return; |
| } |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "Sending connection failure event to " + packageName); |
| } |
| int itemCount = listenersTracker.beginBroadcast(); |
| for (int i = 0; i < itemCount; i++) { |
| try { |
| listenersTracker.getBroadcastItem(i).onConnectionStatus(matchingSuggestion, |
| internalConnectionEventToSuggestionFailureCode(connectionEvent)); |
| } catch (RemoteException e) { |
| Log.e(TAG, "sendNetworkCallback: remote exception -- " + e); |
| } |
| } |
| listenersTracker.finishBroadcast(); |
| } |
| |
| private @WifiManager.SuggestionConnectionStatusCode |
| int internalConnectionEventToSuggestionFailureCode(int connectionEvent) { |
| switch (connectionEvent) { |
| case WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION: |
| case WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT: |
| return WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_ASSOCIATION; |
| case WifiMetrics.ConnectionEvent.FAILURE_SSID_TEMP_DISABLED: |
| case WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE: |
| return WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_AUTHENTICATION; |
| case WifiMetrics.ConnectionEvent.FAILURE_DHCP: |
| return WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_IP_PROVISIONING; |
| default: |
| return WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_UNKNOWN; |
| } |
| } |
| |
| /** |
| * Register a SuggestionUserApprovalStatusListener on user approval status changes. |
| * @param listener ISuggestionUserApprovalStatusListener instance to add. |
| * @param uid uid of the app. |
| */ |
| public void addSuggestionUserApprovalStatusListener( |
| @NonNull ISuggestionUserApprovalStatusListener listener, String packageName, int uid) { |
| RemoteCallbackList<ISuggestionUserApprovalStatusListener> listenersTracker = |
| mSuggestionUserApprovalStatusListenerPerApp.get(packageName); |
| if (listenersTracker == null) { |
| listenersTracker = new RemoteCallbackList<>(); |
| } |
| listenersTracker.register(listener); |
| mSuggestionUserApprovalStatusListenerPerApp.put(packageName, listenersTracker); |
| try { |
| listener.onUserApprovalStatusChange( |
| getNetworkSuggestionUserApprovalStatus(uid, packageName)); |
| } catch (RemoteException e) { |
| Log.e(TAG, "sendUserApprovalStatusChange: remote exception -- " + e); |
| } |
| } |
| |
| /** |
| * Unregister a listener on on user approval status changes. |
| * @param listener ISuggestionUserApprovalStatusListener instance to remove. |
| * @param uid uid of the app. |
| */ |
| public void removeSuggestionUserApprovalStatusListener( |
| @NonNull ISuggestionUserApprovalStatusListener listener, String packageName, int uid) { |
| RemoteCallbackList<ISuggestionUserApprovalStatusListener> listenersTracker = |
| mSuggestionUserApprovalStatusListenerPerApp.get(packageName); |
| if (listenersTracker == null || !listenersTracker.unregister(listener)) { |
| Log.w(TAG, "removeSuggestionUserApprovalStatusListener: Listener from " + packageName |
| + " already removed."); |
| return; |
| } |
| if (listenersTracker != null && listenersTracker.getRegisteredCallbackCount() == 0) { |
| mSuggestionUserApprovalStatusListenerPerApp.remove(packageName); |
| } |
| } |
| |
| /** |
| * Register a SuggestionConnectionStatusListener on network connection failure. |
| * @param listener ISuggestionNetworkCallback instance to add. |
| * @param uid uid of the app. |
| * @return true if succeed otherwise false. |
| */ |
| public boolean registerSuggestionConnectionStatusListener( |
| @NonNull ISuggestionConnectionStatusListener listener, |
| String packageName, int uid) { |
| if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(uid)) { |
| Log.e(TAG, "UID " + uid + " not visible to the current user"); |
| return false; |
| } |
| RemoteCallbackList<ISuggestionConnectionStatusListener> listenersTracker = |
| mSuggestionStatusListenerPerApp.get(packageName); |
| if (listenersTracker == null) { |
| listenersTracker = new RemoteCallbackList<>(); |
| } |
| listenersTracker.register(listener); |
| mSuggestionStatusListenerPerApp.put(packageName, listenersTracker); |
| return true; |
| } |
| |
| /** |
| * Unregister a listener on network connection failure. |
| * @param listener ISuggestionNetworkCallback instance to remove. |
| * @param uid uid of the app. |
| */ |
| public void unregisterSuggestionConnectionStatusListener( |
| @NonNull ISuggestionConnectionStatusListener listener, String packageName, int uid) { |
| if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(uid)) { |
| Log.e(TAG, "UID " + uid + " not visible to the current user"); |
| return; |
| } |
| RemoteCallbackList<ISuggestionConnectionStatusListener> listenersTracker = |
| mSuggestionStatusListenerPerApp.get(packageName); |
| if (listenersTracker == null || !listenersTracker.unregister(listener)) { |
| Log.w(TAG, "unregisterSuggestionConnectionStatusListener: Listener from " + packageName |
| + " already unregister."); |
| } |
| if (listenersTracker != null && listenersTracker.getRegisteredCallbackCount() == 0) { |
| mSuggestionStatusListenerPerApp.remove(packageName); |
| } |
| } |
| |
| /** |
| * When SIM state changes, check if carrier privileges changes for app. |
| * If app changes from privileged to not privileged, remove all suggestions and reset state. |
| * If app changes from not privileges to privileged, set target carrier id for all suggestions. |
| */ |
| public void resetCarrierPrivilegedApps() { |
| Log.w(TAG, "SIM state is changed!"); |
| Iterator<Map.Entry<String, PerAppInfo>> iter = |
| mActiveNetworkSuggestionsPerApp.entrySet().iterator(); |
| while (iter.hasNext()) { |
| PerAppInfo appInfo = iter.next().getValue(); |
| int carrierId = mWifiCarrierInfoManager |
| .getCarrierIdForPackageWithCarrierPrivileges(appInfo.packageName); |
| if (carrierId == appInfo.carrierId) { |
| continue; |
| } |
| if (carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) { |
| Log.i(TAG, "Carrier privilege revoked for " + appInfo.packageName); |
| removeInternal(List.of(), appInfo.packageName, appInfo); |
| iter.remove(); |
| continue; |
| } |
| Log.i(TAG, "Carrier privilege granted for " + appInfo.packageName); |
| appInfo.carrierId = carrierId; |
| for (ExtendedWifiNetworkSuggestion ewns : appInfo.extNetworkSuggestions.values()) { |
| ewns.wns.wifiConfiguration.carrierId = carrierId; |
| } |
| } |
| saveToStore(); |
| } |
| |
| /** |
| * Resets all sim networks state. |
| */ |
| public void resetSimNetworkSuggestions() { |
| mActiveNetworkSuggestionsPerApp.values().stream() |
| .flatMap(e -> e.extNetworkSuggestions.values().stream()) |
| .forEach(ewns -> ewns.anonymousIdentity = null); |
| saveToStore(); |
| } |
| |
| /** |
| * Set auto-join enable/disable for suggestion network |
| * @param config WifiConfiguration which is to change. |
| * @param choice true to enable auto-join, false to disable. |
| * @return true on success, false otherwise (e.g. if no match suggestion exists). |
| */ |
| public boolean allowNetworkSuggestionAutojoin(WifiConfiguration config, boolean choice) { |
| if (!config.fromWifiNetworkSuggestion) { |
| Log.e(TAG, "allowNetworkSuggestionAutojoin: on non-suggestion network: " |
| + config); |
| return false; |
| } |
| |
| if (config.isPasspoint()) { |
| if (!mWifiInjector.getPasspointManager().enableAutojoin(config.getProfileKey(), |
| null, choice)) { |
| return false; |
| } |
| } |
| |
| Set<ExtendedWifiNetworkSuggestion> matchingExtendedWifiNetworkSuggestions = |
| getMatchedSuggestionsWithSameProfileKey( |
| getNetworkSuggestionsForWifiConfiguration(config, config.BSSID), config); |
| if (matchingExtendedWifiNetworkSuggestions.isEmpty()) { |
| Log.e(TAG, "allowNetworkSuggestionAutojoin: network is missing: " |
| + config); |
| return false; |
| } |
| for (ExtendedWifiNetworkSuggestion ewns : matchingExtendedWifiNetworkSuggestions) { |
| ewns.isAutojoinEnabled = choice; |
| } |
| saveToStore(); |
| return true; |
| } |
| |
| /** |
| * Get the filtered ScanResults which may be authenticated by the suggested configurations. |
| * @param wifiNetworkSuggestions The list of {@link WifiNetworkSuggestion} |
| * @param scanResults The list of {@link ScanResult} |
| * @return The filtered ScanResults |
| */ |
| @NonNull |
| public Map<WifiNetworkSuggestion, List<ScanResult>> getMatchingScanResults( |
| @NonNull List<WifiNetworkSuggestion> wifiNetworkSuggestions, |
| @NonNull List<ScanResult> scanResults) { |
| Map<WifiNetworkSuggestion, List<ScanResult>> filteredScanResults = new HashMap<>(); |
| if (wifiNetworkSuggestions == null || wifiNetworkSuggestions.isEmpty() |
| || scanResults == null || scanResults.isEmpty()) { |
| return filteredScanResults; |
| } |
| for (WifiNetworkSuggestion suggestion : wifiNetworkSuggestions) { |
| if (suggestion == null || suggestion.wifiConfiguration == null) { |
| continue; |
| } |
| filteredScanResults.put(suggestion, |
| getMatchingScanResultsForSuggestion(suggestion, scanResults)); |
| } |
| |
| return filteredScanResults; |
| } |
| |
| private List<ScanResult> getMatchingScanResultsForSuggestion(WifiNetworkSuggestion suggestion, |
| List<ScanResult> scanResults) { |
| if (suggestion.passpointConfiguration != null) { |
| return mWifiInjector.getPasspointManager().getMatchingScanResults( |
| suggestion.passpointConfiguration, scanResults); |
| } else { |
| return getMatchingScanResults(suggestion.wifiConfiguration, scanResults); |
| } |
| } |
| |
| /** |
| * Get the filtered ScanResults which may be authenticated by the {@link WifiConfiguration}. |
| * @param wifiConfiguration The instance of {@link WifiConfiguration} |
| * @param scanResults The list of {@link ScanResult} |
| * @return The filtered ScanResults |
| */ |
| @NonNull |
| private List<ScanResult> getMatchingScanResults( |
| @NonNull WifiConfiguration wifiConfiguration, |
| @NonNull List<ScanResult> scanResults) { |
| ScanResultMatchInfo matchInfoFromConfigration = |
| ScanResultMatchInfo.fromWifiConfiguration(wifiConfiguration); |
| if (matchInfoFromConfigration == null) { |
| return new ArrayList<>(); |
| } |
| List<ScanResult> filteredScanResult = new ArrayList<>(); |
| for (ScanResult scanResult : scanResults) { |
| if (matchInfoFromConfigration.equals(ScanResultMatchInfo.fromScanResult(scanResult))) { |
| filteredScanResult.add(scanResult); |
| } |
| } |
| |
| return filteredScanResult; |
| } |
| |
| /** |
| * Add the suggestion update event listener |
| */ |
| public void addOnSuggestionUpdateListener(OnSuggestionUpdateListener listener) { |
| mListeners.add(listener); |
| } |
| |
| /** |
| * When a saved open network has a same network suggestion which is from app has |
| * NETWORK_CARRIER_PROVISIONING permission, also that app suggested secure network suggestion |
| * for same carrier with higher or equal priority and Auto-Join enabled, also that secure |
| * network is in the range. The saved open network will be ignored during the network selection. |
| * TODO (b/142035508): revert all these changes once we build infra needed to solve this. |
| * @param configuration Saved open network to check if it should be ignored. |
| * @param scanDetails Available ScanDetail nearby. |
| * @return True if the open network should be ignored, false otherwise. |
| */ |
| public boolean shouldBeIgnoredBySecureSuggestionFromSameCarrier( |
| @NonNull WifiConfiguration configuration, List<ScanDetail> scanDetails) { |
| if (!mResources.getBoolean( |
| R.bool.config_wifiIgnoreOpenSavedNetworkWhenSecureSuggestionAvailable)) { |
| return false; |
| } |
| if (configuration == null || scanDetails == null || !configuration.isOpenNetwork()) { |
| return false; |
| } |
| Set<ExtendedWifiNetworkSuggestion> matchedExtSuggestions = |
| getNetworkSuggestionsForWifiConfiguration(configuration, null); |
| if (matchedExtSuggestions == null || matchedExtSuggestions.isEmpty()) { |
| return false; |
| } |
| matchedExtSuggestions = matchedExtSuggestions.stream().filter(ewns -> |
| mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(ewns.perAppInfo.uid)) |
| .collect(Collectors.toSet()); |
| if (matchedExtSuggestions.isEmpty()) { |
| return false; |
| } |
| for (ExtendedWifiNetworkSuggestion ewns : matchedExtSuggestions) { |
| if (hasSecureSuggestionFromSameCarrierAvailable(ewns, scanDetails)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Check the suggestion user approval status. |
| */ |
| private @WifiManager.SuggestionUserApprovalStatus int getNetworkSuggestionUserApprovalStatus( |
| int uid, String packageName) { |
| if (mAppOps.unsafeCheckOpNoThrow(OPSTR_CHANGE_WIFI_STATE, uid, packageName) |
| == AppOpsManager.MODE_IGNORED) { |
| return WifiManager.STATUS_SUGGESTION_APPROVAL_REJECTED_BY_USER; |
| } |
| if (!mActiveNetworkSuggestionsPerApp.containsKey(packageName)) { |
| return WifiManager.STATUS_SUGGESTION_APPROVAL_UNKNOWN; |
| } |
| PerAppInfo info = mActiveNetworkSuggestionsPerApp.get(packageName); |
| if (info.hasUserApproved) { |
| return WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER; |
| } |
| if (info.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) { |
| return WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_CARRIER_PRIVILEGE; |
| } |
| return WifiManager.STATUS_SUGGESTION_APPROVAL_PENDING; |
| } |
| |
| private boolean hasSecureSuggestionFromSameCarrierAvailable( |
| ExtendedWifiNetworkSuggestion extendedWifiNetworkSuggestion, |
| List<ScanDetail> scanDetails) { |
| boolean isOpenSuggestionMetered = WifiConfiguration.isMetered( |
| extendedWifiNetworkSuggestion.wns.wifiConfiguration, null); |
| Set<ExtendedWifiNetworkSuggestion> secureExtSuggestions = new HashSet<>(); |
| for (ExtendedWifiNetworkSuggestion ewns : extendedWifiNetworkSuggestion.perAppInfo |
| .extNetworkSuggestions.values()) { |
| // Open network and auto-join disable suggestion, ignore. |
| if (isOpenSuggestion(ewns) || !ewns.isAutojoinEnabled) { |
| continue; |
| } |
| // From different carrier as open suggestion, ignore. |
| if (getCarrierIdFromSuggestion(ewns) |
| != getCarrierIdFromSuggestion(extendedWifiNetworkSuggestion)) { |
| continue; |
| } |
| // Secure and open has different meterness, ignore |
| if (WifiConfiguration.isMetered(ewns.wns.wifiConfiguration, null) |
| != isOpenSuggestionMetered) { |
| continue; |
| } |
| // Low priority than open suggestion, ignore. |
| if (ewns.wns.wifiConfiguration.priority |
| < extendedWifiNetworkSuggestion.wns.wifiConfiguration.priority) { |
| continue; |
| } |
| WifiConfiguration wcmConfig = mWifiConfigManager |
| .getConfiguredNetwork(ewns.wns.wifiConfiguration.getProfileKey()); |
| // Network selection is disabled, ignore. |
| if (wcmConfig != null && !wcmConfig.getNetworkSelectionStatus().isNetworkEnabled()) { |
| continue; |
| } |
| secureExtSuggestions.add(ewns); |
| } |
| |
| if (secureExtSuggestions.isEmpty()) { |
| return false; |
| } |
| List<ScanResult> scanResults = scanDetails.stream().map(ScanDetail::getScanResult) |
| .collect(Collectors.toList()); |
| // Check if the secure suggestion is in the range. |
| for (ExtendedWifiNetworkSuggestion ewns : secureExtSuggestions) { |
| if (!getMatchingScanResultsForSuggestion(ewns.wns, scanResults).isEmpty()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Set the app treated as cross carrier provider. That can suggest for any carrier |
| * @param packageName App name to set. |
| * @param enabled True to set app treated as cross carrier provider, false otherwise. |
| */ |
| public void setAppWorkingAsCrossCarrierProvider(String packageName, boolean enabled) { |
| if (enabled) { |
| mCrossCarrierProvidersSet.add(packageName); |
| } else { |
| mCrossCarrierProvidersSet.remove(packageName); |
| } |
| } |
| |
| /** |
| * Check whether the app is treated as a cross carrier provider or not. |
| * @param packageName App name to check |
| * @return True for app is treated as a carrier provider, false otherwise. |
| */ |
| public boolean isAppWorkingAsCrossCarrierProvider(String packageName) { |
| return mCrossCarrierProvidersSet.contains(packageName); |
| } |
| |
| /** |
| * Store Anonymous Identity for SIM based suggestion after connection. |
| */ |
| public void setAnonymousIdentity(WifiConfiguration config) { |
| if (config.isPasspoint() || !config.fromWifiNetworkSuggestion) { |
| return; |
| } |
| if (config.enterpriseConfig == null |
| || !config.enterpriseConfig.isAuthenticationSimBased()) { |
| Log.e(TAG, "Network is not SIM based, AnonymousIdentity is invalid"); |
| } |
| Set<ExtendedWifiNetworkSuggestion> matchedSuggestionSet = |
| getMatchedSuggestionsWithSameProfileKey( |
| getNetworkSuggestionsForWifiConfiguration(config, config.BSSID), config); |
| if (matchedSuggestionSet.isEmpty()) { |
| Log.wtf(TAG, "Current connected SIM based network suggestion is missing!"); |
| return; |
| } |
| for (ExtendedWifiNetworkSuggestion ewns : matchedSuggestionSet) { |
| ewns.anonymousIdentity = config.enterpriseConfig.getAnonymousIdentity(); |
| } |
| saveToStore(); |
| } |
| |
| private boolean isOpenSuggestion(ExtendedWifiNetworkSuggestion extendedWifiNetworkSuggestion) { |
| if (extendedWifiNetworkSuggestion.wns.passpointConfiguration != null) { |
| return false; |
| } |
| return extendedWifiNetworkSuggestion.wns.wifiConfiguration.isOpenNetwork(); |
| } |
| |
| private void onUserConnectChoiceSet(Collection<WifiConfiguration> networks, String choiceKey, |
| int rssi) { |
| Set<String> networkKeys = networks.stream() |
| .filter(config -> config.fromWifiNetworkSuggestion) |
| .map(WifiConfiguration::getProfileKey) |
| .collect(Collectors.toSet()); |
| mActiveNetworkSuggestionsPerApp.values().stream() |
| .flatMap(e -> e.extNetworkSuggestions.values().stream()) |
| .forEach(ewns -> { |
| String profileKey = ewns |
| .createInternalWifiConfiguration(mWifiCarrierInfoManager) |
| .getProfileKey(); |
| if (TextUtils.equals(profileKey, choiceKey)) { |
| ewns.connectChoice = null; |
| ewns.connectChoiceRssi = 0; |
| } else if (networkKeys.contains(profileKey)) { |
| ewns.connectChoice = choiceKey; |
| ewns.connectChoiceRssi = rssi; |
| } |
| }); |
| saveToStore(); |
| } |
| |
| private void onUserConnectChoiceRemove(String choiceKey) { |
| mActiveNetworkSuggestionsPerApp.values().stream() |
| .flatMap(e -> e.extNetworkSuggestions.values().stream()) |
| .filter(ewns -> TextUtils.equals(ewns.connectChoice, choiceKey)) |
| .forEach(ewns -> { |
| ewns.connectChoice = null; |
| ewns.connectChoiceRssi = 0; |
| }); |
| saveToStore(); |
| } |
| |
| private void onSuggestionUserApprovalStatusChanged(int uid, String packageName) { |
| RemoteCallbackList<ISuggestionUserApprovalStatusListener> listenersTracker = |
| mSuggestionUserApprovalStatusListenerPerApp.get(packageName); |
| if (listenersTracker == null || listenersTracker.getRegisteredCallbackCount() == 0) { |
| return; |
| } |
| |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "Sending user approval status change event to " + packageName); |
| } |
| int itemCount = listenersTracker.beginBroadcast(); |
| for (int i = 0; i < itemCount; i++) { |
| try { |
| listenersTracker.getBroadcastItem(i).onUserApprovalStatusChange( |
| getNetworkSuggestionUserApprovalStatus(uid, packageName)); |
| } catch (RemoteException e) { |
| Log.e(TAG, "sendUserApprovalStatusChange: remote exception -- " + e); |
| } |
| } |
| listenersTracker.finishBroadcast(); |
| } |
| |
| private boolean areCarrierMergedSuggestionsAllowed(int subId, String packageName) { |
| if (isAppWorkingAsCrossCarrierProvider(packageName)) { |
| return true; |
| } |
| |
| return mWifiCarrierInfoManager.areMergedCarrierWifiNetworksAllowed(subId); |
| } |
| |
| /** |
| * Dump of {@link WifiNetworkSuggestionsManager}. |
| */ |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| pw.println("Dump of WifiNetworkSuggestionsManager"); |
| pw.println("WifiNetworkSuggestionsManager - Networks Begin ----"); |
| final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage(); |
| for (Map.Entry<String, PerAppInfo> networkSuggestionsEntry |
| : mActiveNetworkSuggestionsPerApp.entrySet()) { |
| pw.println("Package Name: " + networkSuggestionsEntry.getKey()); |
| PerAppInfo appInfo = networkSuggestionsEntry.getValue(); |
| pw.println("Has user approved: " + appInfo.hasUserApproved); |
| pw.println("Has carrier privileges: " |
| + (appInfo.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID)); |
| pw.println("Is active scorer: " + appInfo.packageName.equals(activeScorerPackage)); |
| for (ExtendedWifiNetworkSuggestion extNetworkSuggestion |
| : appInfo.extNetworkSuggestions.values()) { |
| pw.println("Network: " + extNetworkSuggestion); |
| } |
| } |
| pw.println("WifiNetworkSuggestionsManager - Networks End ----"); |
| } |
| |
| public void resetNotification() { |
| mNotificationManager.cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE); |
| mNotificationUpdateTime = 0; |
| } |
| } |