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