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