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