New internal interface method to get the active scorer metadata.

Returns the cached NetworkScorerAppData from the connected service.

Test: runtest frameworks-services -c com.android.server.NetworkScoreServiceTest
Bug: 34773276
Change-Id: Ia8986e770c4ede0c0f7e2ad4f5bc0a0117a9ae2b
diff --git a/core/java/android/net/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl
index 82432c7..9cc256e 100644
--- a/core/java/android/net/INetworkScoreService.aidl
+++ b/core/java/android/net/INetworkScoreService.aidl
@@ -18,6 +18,7 @@
 
 import android.net.INetworkScoreCache;
 import android.net.NetworkKey;
+import android.net.NetworkScorerAppManager;
 import android.net.RecommendationRequest;
 import android.net.RecommendationResult;
 import android.net.ScoredNetwork;
@@ -130,4 +131,6 @@
      */
     oneway void requestRecommendationAsync(in RecommendationRequest request,
                                            in RemoteCallback remoteCallback);
+
+    NetworkScorerAppManager.NetworkScorerAppData getActiveScorer();
 }
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index 57cf1a5..2875580 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -186,6 +186,20 @@
     }
 
     /**
+     * Returns metadata about the active scorer or <code>null</code> if there is no active scorer.
+     *
+     * @hide
+     */
+    @Nullable
+    public NetworkScorerAppData getActiveScorer() {
+        try {
+            return mService.getActiveScorer();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Update network scores.
      *
      * <p>This may be called at any time to re-score active networks. Scores will generally be
diff --git a/core/java/android/net/NetworkScorerAppManager.aidl b/core/java/android/net/NetworkScorerAppManager.aidl
new file mode 100644
index 0000000..d968343
--- /dev/null
+++ b/core/java/android/net/NetworkScorerAppManager.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable NetworkScorerAppManager.NetworkScorerAppData;
diff --git a/core/java/android/net/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java
index 9dcf4f4..f3cbb52 100644
--- a/core/java/android/net/NetworkScorerAppManager.java
+++ b/core/java/android/net/NetworkScorerAppManager.java
@@ -24,6 +24,8 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -54,7 +56,7 @@
     /**
      * Holds metadata about a discovered network scorer/recommendation application.
      */
-    public static class NetworkScorerAppData {
+    public static final class NetworkScorerAppData implements Parcelable {
         /** UID of the scorer app. */
         public final int packageUid;
         private final ComponentName mRecommendationService;
@@ -64,6 +66,35 @@
             this.mRecommendationService = recommendationServiceComp;
         }
 
+        protected NetworkScorerAppData(Parcel in) {
+            packageUid = in.readInt();
+            mRecommendationService = ComponentName.readFromParcel(in);
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(packageUid);
+            ComponentName.writeToParcel(mRecommendationService, dest);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final Creator<NetworkScorerAppData> CREATOR =
+                new Creator<NetworkScorerAppData>() {
+                    @Override
+                    public NetworkScorerAppData createFromParcel(Parcel in) {
+                        return new NetworkScorerAppData(in);
+                    }
+
+                    @Override
+                    public NetworkScorerAppData[] newArray(int size) {
+                        return new NetworkScorerAppData[size];
+                    }
+                };
+
         public String getRecommendationServicePackageName() {
             return mRecommendationService.getPackageName();
         }
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 5fe8b1a..0ac51b9 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -53,6 +53,7 @@
 import android.os.IRemoteCallback;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -676,6 +677,10 @@
         }
     }
 
+    private boolean isCallerSystemProcess(int callingUid) {
+        return callingUid == Process.SYSTEM_UID;
+    }
+
     /**
      * Obtain the package name of the current active network scorer.
      *
@@ -692,6 +697,27 @@
         return null;
     }
 
+
+    /**
+     * Returns metadata about the active scorer or <code>null</code> if there is no active scorer.
+     */
+    @Override
+    public NetworkScorerAppData getActiveScorer() {
+        // Only the system can access this data.
+        if (isCallerSystemProcess(getCallingUid()) || callerCanRequestScores()) {
+            synchronized (mServiceConnectionLock) {
+                if (mServiceConnection != null) {
+                    return mServiceConnection.mAppData;
+                }
+            }
+        } else {
+            throw new SecurityException(
+                    "Caller is neither the system process nor a score requester.");
+        }
+
+        return null;
+    }
+
     @Override
     public void disableScoring() {
         // Only the active scorer or the system should be allowed to disable scoring.
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index fa9e9a8..3a88e9c 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -829,6 +829,50 @@
         assertEquals(expectedList, actualList);
     }
 
+    @Test
+    public void testGetActiveScorer_notConnected_canRequestScores() throws Exception {
+        when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        assertNull(mNetworkScoreService.getActiveScorer());
+    }
+
+    @Test
+    public void testGetActiveScorer_notConnected_canNotRequestScores() throws Exception {
+        when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+        try {
+            mNetworkScoreService.getActiveScorer();
+            fail("SecurityException expected.");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testGetActiveScorer_connected_canRequestScores()
+            throws Exception {
+        when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        NetworkScorerAppData expectedAppData =
+                new NetworkScorerAppData(Binder.getCallingUid(), RECOMMENDATION_SERVICE_COMP);
+        bindToScorer(expectedAppData);
+        assertEquals(expectedAppData, mNetworkScoreService.getActiveScorer());
+    }
+
+    @Test
+    public void testGetActiveScorer_connected_canNotRequestScores()
+            throws Exception {
+        when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+        bindToScorer(false);
+        try {
+            mNetworkScoreService.getActiveScorer();
+            fail("SecurityException expected.");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
     // "injects" the mock INetworkRecommendationProvider into the NetworkScoreService.
     private void injectProvider() {
         when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER);
@@ -849,9 +893,13 @@
     }
 
     private void bindToScorer(boolean callerIsScorer) {
-        final int callingUid = callerIsScorer ? Binder.getCallingUid() : 0;
+        final int callingUid = callerIsScorer ? Binder.getCallingUid() : Binder.getCallingUid() + 1;
         NetworkScorerAppData appData =
                 new NetworkScorerAppData(callingUid, RECOMMENDATION_SERVICE_COMP);
+        bindToScorer(appData);
+    }
+
+    private void bindToScorer(NetworkScorerAppData appData) {
         when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(appData);
         when(mContext.bindServiceAsUser(isA(Intent.class), isA(ServiceConnection.class), anyInt(),
                 isA(UserHandle.class))).thenReturn(true);