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