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