Add NetworkScoreManager.disableScoring().

Allows the active scorer app to disable itself.

Change-Id: I7bcdc3aafb95af4ea0b110b01b08ab4daf7a137f
diff --git a/core/java/android/net/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl
index 626bd2a..43869264 100644
--- a/core/java/android/net/INetworkScoreService.aidl
+++ b/core/java/android/net/INetworkScoreService.aidl
@@ -48,6 +48,12 @@
     boolean setActiveScorer(in String packageName);
 
     /**
+     * Disable the current active scorer and clear existing scores.
+     * @throws SecurityException if the caller is not the current scorer or the system.
+     */
+    void disableScoring();
+
+    /**
      * Register a network subsystem for scoring.
      *
      * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}.
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index b497c6e..2fc3892 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -205,6 +205,20 @@
     }
 
     /**
+     * Turn off network scoring.
+     *
+     * <p>May only be called by the current scorer app, or the system.
+     *
+     * @throws SecurityException if the caller is neither the active scorer nor the system.
+     */
+    public void disableScoring() throws SecurityException {
+        try {
+            mService.disableScoring();
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
      * Request scoring for networks.
      *
      * <p>Note that this is just a helper method to assemble the broadcast, and will run in the
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 538501b..3e2f260 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -27,6 +27,7 @@
 import android.net.NetworkScorerAppManager;
 import android.net.NetworkScorerAppManager.NetworkScorerAppData;
 import android.net.ScoredNetwork;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.text.TextUtils;
 import android.util.Log;
@@ -131,17 +132,44 @@
     @Override
     public boolean setActiveScorer(String packageName) {
         mContext.enforceCallingOrSelfPermission(permission.BROADCAST_SCORE_NETWORKS, TAG);
-        // Preemptively clear scores even though the set operation could fail. We do this for safety
-        // as scores should never be compared across apps; in practice, Settings should only be
-        // allowing valid apps to be set as scorers, so failure here should be rare.
-        clearInternal();
-        boolean result = NetworkScorerAppManager.setActiveScorer(mContext, packageName);
-        if (result) {
-            Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED);
-            intent.putExtra(NetworkScoreManager.EXTRA_NEW_SCORER, packageName);
-            mContext.sendBroadcast(intent);
+        return setScorerInternal(packageName);
+    }
+
+    @Override
+    public void disableScoring() {
+        // Only the active scorer or the system (who can broadcast BROADCAST_SCORE_NETOWRKS) should
+        // be allowed to disable scoring.
+        if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) ||
+                mContext.checkCallingOrSelfPermission(permission.BROADCAST_SCORE_NETWORKS) ==
+                        PackageManager.PERMISSION_GRANTED) {
+            // The return value is discarded here because at this point, the call should always
+            // succeed. The only reason for failure is if the new package is not a valid scorer, but
+            // we're disabling scoring altogether here.
+            setScorerInternal(null /* packageName */);
+        } else {
+            throw new SecurityException(
+                    "Caller is neither the active scorer nor the scorer manager.");
         }
-        return result;
+    }
+
+    /** Set the active scorer. Callers are responsible for checking permissions as appropriate. */
+    private boolean setScorerInternal(String packageName) {
+        long token = Binder.clearCallingIdentity();
+        try {
+            // Preemptively clear scores even though the set operation could fail. We do this for
+            // safety as scores should never be compared across apps; in practice, Settings should
+            // only be allowing valid apps to be set as scorers, so failure here should be rare.
+            clearInternal();
+            boolean result = NetworkScorerAppManager.setActiveScorer(mContext, packageName);
+            if (result) {
+                Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED);
+                intent.putExtra(NetworkScoreManager.EXTRA_NEW_SCORER, packageName);
+                mContext.sendBroadcast(intent);
+            }
+            return result;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     /** Clear scores. Callers are responsible for checking permissions as appropriate. */