NetworkScoreManager: Add @SystemApi for wifi mainline module

Add formal API's for API's that the wifi mainline module uses:
a) NetworkScoreManager.requestNetworkScores - Request new network scores.
b) NetworkScoreManager.registerNetworkScoreCallback - @SystemApi wrapper
over the existing registerNetworkScoreCache.
c) NetworkKey.createFromScanResult - Create NetworkKey from ScanResult.

Also,
a) Converted the existing WifiNetworkScoreCache callback implementation to
support both the new @SystemApi (used by wifi) and the old @hide API
(used by settings).
b) Stopped invoking dump on all the callbacks from NetworkScoreService.
The dump of each callback should be invoked at their client site (i.e
wifi service should dump state of the callback it registers with the
service).
c) Added a helper method |dumpWithLatestScanResults| to help dump the
state of the WifiNetworkScoreCache from wifi service.

Bug: 144487252
Test: a) Device boots up and connects to wifi network.
b) Manually verified that network scores are being requested & updated
with the new interface.

Change-Id: Id5a66189150e7a088127519373a832f63bdd12ac
Merged-In: Id5a66189150e7a088127519373a832f63bdd12ac
(cherry-picked from c278f80c282e1ec4563b75b2c3f7c169e3d26023)
diff --git a/api/system-current.txt b/api/system-current.txt
index ac8581a..0b8cdf2 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4179,6 +4179,7 @@
 
   public class NetworkKey implements android.os.Parcelable {
     ctor public NetworkKey(android.net.WifiKey);
+    method @Nullable public static android.net.NetworkKey createFromScanResult(@Nullable android.net.wifi.ScanResult);
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkKey> CREATOR;
@@ -4201,16 +4202,23 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, "android.permission.REQUEST_NETWORK_SCORES"}) public boolean clearScores() throws java.lang.SecurityException;
     method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, "android.permission.REQUEST_NETWORK_SCORES"}) public void disableScoring() throws java.lang.SecurityException;
     method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, "android.permission.REQUEST_NETWORK_SCORES"}) public String getActiveScorerPackage();
+    method @RequiresPermission("android.permission.REQUEST_NETWORK_SCORES") public void registerNetworkScoreCallback(int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.NetworkScoreManager.NetworkScoreCallback) throws java.lang.SecurityException;
+    method @RequiresPermission("android.permission.REQUEST_NETWORK_SCORES") public boolean requestScores(@NonNull android.net.NetworkKey[]) throws java.lang.SecurityException;
     method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, "android.permission.REQUEST_NETWORK_SCORES"}) public boolean setActiveScorer(String) throws java.lang.SecurityException;
-    method @RequiresPermission(android.Manifest.permission.SCORE_NETWORKS) public boolean updateScores(android.net.ScoredNetwork[]) throws java.lang.SecurityException;
-    field public static final String ACTION_CHANGE_ACTIVE = "android.net.scoring.CHANGE_ACTIVE";
+    method @RequiresPermission(android.Manifest.permission.SCORE_NETWORKS) public boolean updateScores(@NonNull android.net.ScoredNetwork[]) throws java.lang.SecurityException;
+    field @Deprecated public static final String ACTION_CHANGE_ACTIVE = "android.net.scoring.CHANGE_ACTIVE";
     field public static final String ACTION_CUSTOM_ENABLE = "android.net.scoring.CUSTOM_ENABLE";
     field public static final String ACTION_RECOMMEND_NETWORKS = "android.net.action.RECOMMEND_NETWORKS";
     field public static final String ACTION_SCORER_CHANGED = "android.net.scoring.SCORER_CHANGED";
-    field public static final String ACTION_SCORE_NETWORKS = "android.net.scoring.SCORE_NETWORKS";
-    field public static final String EXTRA_NETWORKS_TO_SCORE = "networksToScore";
+    field @Deprecated public static final String ACTION_SCORE_NETWORKS = "android.net.scoring.SCORE_NETWORKS";
+    field @Deprecated public static final String EXTRA_NETWORKS_TO_SCORE = "networksToScore";
     field public static final String EXTRA_NEW_SCORER = "newScorer";
-    field public static final String EXTRA_PACKAGE_NAME = "packageName";
+    field @Deprecated public static final String EXTRA_PACKAGE_NAME = "packageName";
+  }
+
+  public static interface NetworkScoreManager.NetworkScoreCallback {
+    method public void clearScores();
+    method public void updateScores(@NonNull java.util.List<android.net.ScoredNetwork>);
   }
 
   public class NetworkStack {
diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java
index 04cb877..9af1407 100644
--- a/core/java/android/net/NetworkKey.java
+++ b/core/java/android/net/NetworkKey.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -27,6 +28,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
@@ -48,6 +51,13 @@
     /** A wifi network, for which {@link #wifiKey} will be populated. */
     public static final int TYPE_WIFI = 1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"TYPE_"}, value = {
+            TYPE_WIFI
+    })
+    public @interface NetworkType {}
+
     /**
      * The type of this network.
      * @see #TYPE_WIFI
@@ -65,7 +75,6 @@
      *
      * @return  A new {@link NetworkKey} instance or <code>null</code> if the given
      *          {@link ScanResult} instance is malformed.
-     * @hide
      */
     @Nullable
     public static NetworkKey createFromScanResult(@Nullable ScanResult result) {
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index 50dd468..f6dc525 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -17,7 +17,9 @@
 package android.net;
 
 import android.Manifest.permission;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
@@ -25,13 +27,16 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
+import android.util.Log;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * Class that manages communication between network subsystems and a network scorer.
@@ -50,19 +55,25 @@
 @SystemApi
 @SystemService(Context.NETWORK_SCORE_SERVICE)
 public class NetworkScoreManager {
+    private static final String TAG = "NetworkScoreManager";
+
     /**
      * Activity action: ask the user to change the active network scorer. This will show a dialog
      * that asks the user whether they want to replace the current active scorer with the one
      * specified in {@link #EXTRA_PACKAGE_NAME}. The activity will finish with RESULT_OK if the
      * active scorer was changed or RESULT_CANCELED if it failed for any reason.
+     * @deprecated No longer sent.
      */
+    @Deprecated
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_CHANGE_ACTIVE = "android.net.scoring.CHANGE_ACTIVE";
 
     /**
      * Extra used with {@link #ACTION_CHANGE_ACTIVE} to specify the new scorer package. Set with
      * {@link android.content.Intent#putExtra(String, String)}.
+     * @deprecated No longer sent.
      */
+    @Deprecated
     public static final String EXTRA_PACKAGE_NAME = "packageName";
 
     /**
@@ -73,7 +84,9 @@
      * configured by the user as well as any open networks.
      *
      * <p class="note">This is a protected intent that can only be sent by the system.
+     * @deprecated Use {@link #ACTION_RECOMMEND_NETWORKS} to bind scorer app instead.
      */
+    @Deprecated
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_SCORE_NETWORKS = "android.net.scoring.SCORE_NETWORKS";
 
@@ -81,7 +94,9 @@
      * Extra used with {@link #ACTION_SCORE_NETWORKS} to specify the networks to be scored, as an
      * array of {@link NetworkKey}s. Can be obtained with
      * {@link android.content.Intent#getParcelableArrayExtra(String)}}.
+     * @deprecated Use {@link #ACTION_RECOMMEND_NETWORKS} to bind scorer app instead.
      */
+    @Deprecated
     public static final String EXTRA_NETWORKS_TO_SCORE = "networksToScore";
 
     /**
@@ -285,7 +300,7 @@
      * @throws SecurityException if the caller is not the active scorer.
      */
     @RequiresPermission(android.Manifest.permission.SCORE_NETWORKS)
-    public boolean updateScores(ScoredNetwork[] networks) throws SecurityException {
+    public boolean updateScores(@NonNull ScoredNetwork[] networks) throws SecurityException {
         try {
             return mService.updateScores(networks);
         } catch (RemoteException e) {
@@ -359,13 +374,21 @@
     /**
      * Request scoring for networks.
      *
-     * @return true if the broadcast was sent, or false if there is no active scorer.
+     * <p>
+     * Note: The results (i.e scores) for these networks, when available will be provided via the
+     * callback registered with {@link #registerNetworkScoreCallback(int, int, Executor,
+     * NetworkScoreCallback)}. The calling module is responsible for registering a callback to
+     * receive the results before requesting new scores via this API.
+     *
+     * @return true if the request was successfully sent, or false if there is no active scorer.
      * @throws SecurityException if the caller does not hold the
      *         {@link permission#REQUEST_NETWORK_SCORES} permission.
+     *
      * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
-    public boolean requestScores(NetworkKey[] networks) throws SecurityException {
+    public boolean requestScores(@NonNull NetworkKey[] networks) throws SecurityException {
         try {
             return mService.requestScores(networks);
         } catch (RemoteException e) {
@@ -431,6 +454,88 @@
     }
 
     /**
+     * Base class for network score cache callback. Should be extended by applications and set
+     * when calling {@link #registerNetworkScoreCallback(int, int, NetworkScoreCallback,
+     * Executor)}
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface NetworkScoreCallback {
+        /**
+         * Called when a new set of network scores are available.
+         * This is triggered in response when the client invokes
+         * {@link #requestScores(NetworkKey[])} to score a new set of networks.
+         *
+         * @param networks List of {@link ScoredNetwork} containing updated scores.
+         */
+        void updateScores(@NonNull List<ScoredNetwork> networks);
+
+        /**
+         * Invokes when all the previously provided scores are no longer valid.
+         */
+        void clearScores();
+    }
+
+    /**
+     * Callback proxy for {@link NetworkScoreCallback} objects.
+     */
+    private class NetworkScoreCallbackProxy extends INetworkScoreCache.Stub {
+        private final Executor mExecutor;
+        private final NetworkScoreCallback mCallback;
+
+        NetworkScoreCallbackProxy(Executor executor, NetworkScoreCallback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void updateScores(@NonNull List<ScoredNetwork> networks) {
+            Binder.clearCallingIdentity();
+            mExecutor.execute(() -> {
+                mCallback.updateScores(networks);
+            });
+        }
+
+        @Override
+        public void clearScores() {
+            Binder.clearCallingIdentity();
+            mExecutor.execute(() -> {
+                mCallback.clearScores();
+            });
+        }
+    }
+
+    /**
+     * Register a network score callback.
+     *
+     * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}
+     * @param filterType the {@link CacheUpdateFilter} to apply
+     * @param callback implementation of {@link NetworkScoreCallback} that will be invoked when the
+     *                 scores change.
+     * @param executor The executor on which to execute the callbacks.
+     * @throws SecurityException if the caller does not hold the
+     *         {@link permission#REQUEST_NETWORK_SCORES} permission.
+     * @throws IllegalArgumentException if a callback is already registered for this type.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
+    public void registerNetworkScoreCallback(@NetworkKey.NetworkType int networkType,
+            @CacheUpdateFilter int filterType,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull NetworkScoreCallback callback) throws SecurityException {
+        if (callback == null || executor == null) {
+            throw new IllegalArgumentException("callback / executor cannot be null");
+        }
+        Log.v(TAG, "registerNetworkScoreCallback: callback=" + callback + ", executor="
+                + executor);
+        // Use the @hide method.
+        registerNetworkScoreCache(
+                networkType, new NetworkScoreCallbackProxy(executor, callback), filterType);
+    }
+
+    /**
      * Determine whether the application with the given UID is the enabled scorer.
      *
      * @param callingUid the UID to check
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 80d7ac9..9a7a4e7 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -60,12 +60,9 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
-import com.android.internal.os.TransferPipe;
-import com.android.internal.telephony.SmsApplication;
 import com.android.internal.util.DumpUtils;
 
 import java.io.FileDescriptor;
-import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -908,17 +905,6 @@
             }
             writer.println("Current scorer: " + currentScorer);
 
-            sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
-                @Override
-                public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
-                    try {
-                        TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
-                    } catch (IOException | RemoteException e) {
-                        writer.println("Failed to dump score cache: " + e);
-                    }
-                }
-            }, getScoreCacheLists());
-
             synchronized (mServiceConnectionLock) {
                 if (mServiceConnection != null) {
                     mServiceConnection.dump(fd, writer, args);
diff --git a/wifi/java/android/net/wifi/WifiNetworkScoreCache.java b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
index b22ae070..5a212a8 100755
--- a/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
+++ b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.net.INetworkScoreCache;
 import android.net.NetworkKey;
+import android.net.NetworkScoreManager;
 import android.net.ScoredNetwork;
 import android.os.Handler;
 import android.os.Process;
@@ -40,7 +41,8 @@
  *
  * @hide
  */
-public class WifiNetworkScoreCache extends INetworkScoreCache.Stub {
+public class WifiNetworkScoreCache extends INetworkScoreCache.Stub
+        implements NetworkScoreManager.NetworkScoreCallback {
     private static final String TAG = "WifiNetworkScoreCache";
     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -246,6 +248,17 @@
     }
 
     @Override protected final void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+        dumpWithLatestScanResults(fd, writer, args, wifiManager.getScanResults());
+    }
+
+    /**
+     * This is directly invoked from within Wifi-Service (on it's instance of this class), hence
+     * avoid making the WifiManager.getScanResults() call to avoid a deadlock.
+     */
+    public final void dumpWithLatestScanResults(
+            FileDescriptor fd, PrintWriter writer, String[] args,
+            List<ScanResult> latestScanResults) {
         mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
         String header = String.format("WifiNetworkScoreCache (%s/%d)",
                 mContext.getPackageName(), Process.myUid());
@@ -256,8 +269,7 @@
                 writer.println("    " + score);
             }
             writer.println("  Network scores for latest ScanResults:");
-            WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
-            for (ScanResult scanResult : wifiManager.getScanResults()) {
+            for (ScanResult scanResult : latestScanResults) {
                 writer.println(
                         "    " + buildNetworkKey(scanResult) + ": " + getNetworkScore(scanResult));
             }