Merge "NetworkScoreManager: Add @SystemApi for wifi mainline module" am: 5b57190f08
am: 0564779e76

Change-Id: I04580ad32eefb2ce4c8df8f1037b8457238c30eb
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));
             }