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