OSDN Git Service

Initial implementation of NetworkScoreManager's backing service.
authorJeff Davidson <jpd@google.com>
Thu, 17 Apr 2014 00:29:40 +0000 (17:29 -0700)
committerJeff Davidson <jpd@google.com>
Mon, 21 Apr 2014 23:04:05 +0000 (16:04 -0700)
This service will ultimately be responsible for propagating scores
down to lower-level network subsystems. For now, it just keeps scores
in memory and exposes these for debugging purposes via "adb shell
dumpsys network_score".

This change also adds provisioning of a default scorer. When
NetworkScoreService is first initialized, it checks to see if it has
ever set a default scorer; if not, it reads a package name from a
build config property and attempts to set it as the default.

Also add autogenerated equals/hashCode methods to all parcelables.

Bug: 14111427
Bug: 13786258
Change-Id: I02271171653d42e12acd240b73b9e23950744f6b

13 files changed:
Android.mk
core/java/android/net/INetworkScoreService.aidl [new file with mode: 0644]
core/java/android/net/NetworkKey.java
core/java/android/net/NetworkScoreManager.java
core/java/android/net/NetworkScorerAppManager.java [moved from core/java/android/net/NetworkScorerApplication.java with 89% similarity]
core/java/android/net/RssiCurve.java
core/java/android/net/ScoredNetwork.java
core/java/android/net/WifiKey.java
core/res/res/values/config.xml
core/res/res/values/symbols.xml
core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java [moved from core/tests/coretests/src/android/net/NetworkScorerApplicationTest.java with 95% similarity]
services/core/java/com/android/server/NetworkScoreService.java [new file with mode: 0644]
services/java/com/android/server/SystemServer.java

index c2910fd..be7e055 100644 (file)
@@ -155,6 +155,7 @@ LOCAL_SRC_FILES += \
        core/java/android/net/INetworkManagementEventObserver.aidl \
        core/java/android/net/INetworkPolicyListener.aidl \
        core/java/android/net/INetworkPolicyManager.aidl \
+       core/java/android/net/INetworkScoreService.aidl \
        core/java/android/net/INetworkStatsService.aidl \
        core/java/android/net/INetworkStatsSession.aidl \
        core/java/android/net/nsd/INsdManager.aidl \
diff --git a/core/java/android/net/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl
new file mode 100644 (file)
index 0000000..a72d9a0
--- /dev/null
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2014, 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;
+
+import android.net.ScoredNetwork;
+
+/**
+ * A service for updating network scores from a network scorer application.
+ * @hide
+ */
+interface INetworkScoreService
+{
+    /**
+     * Update scores.
+     * @return whether the update was successful.
+     * @throws SecurityException if the caller is not the current active scorer.
+     */
+    boolean updateScores(in ScoredNetwork[] networks);
+
+    /**
+     * Clear all scores.
+     * @return whether the clear was successful.
+     * @throws SecurityException if the caller is neither the current active scorer nor the scorer
+     * manager.
+     */
+    boolean clearScores();
+
+    /**
+     * Set the active scorer and clear existing scores.
+     * @param packageName the package name of the new scorer to use.
+     * @return true if the operation succeeded, or false if the new package is not a valid scorer.
+     * @throws SecurityException if the caller is not the scorer manager.
+     */
+    boolean setActiveScorer(in String packageName);
+}
index cc3ad3e..bc19658 100644 (file)
@@ -19,11 +19,19 @@ package android.net;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Objects;
+
 /**
  * Information which identifies a specific network.
  *
  * @hide
  */
+// NOTE: Ideally, we would abstract away the details of what identifies a network of a specific
+// type, so that all networks appear the same and can be scored without concern to the network type
+// itself. However, because no such cross-type identifier currently exists in the Android framework,
+// and because systems might obtain information about networks from sources other than Android
+// devices, we need to provide identifying details about each specific network type (wifi, cell,
+// etc.) so that clients can pull out these details depending on the type of network.
 public class NetworkKey implements Parcelable {
 
     /** A wifi network, for which {@link #wifiKey} will be populated. */
@@ -79,6 +87,21 @@ public class NetworkKey implements Parcelable {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        NetworkKey that = (NetworkKey) o;
+
+        return type == that.type && Objects.equals(wifiKey, that.wifiKey);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(type, wifiKey);
+    }
+
+    @Override
     public String toString() {
         switch (type) {
             case TYPE_WIFI:
index 3430547..5e61613 100644 (file)
@@ -19,6 +19,9 @@ package android.net;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 
 /**
  * Class that manages communication between network subsystems and a network scorer.
@@ -40,7 +43,7 @@ import android.content.Context;
  * <p>The system keeps track of a default scorer application; at any time, only this application
  * will receive {@link #ACTION_SCORE_NETWORKS} broadcasts and will be permitted to call
  * {@link #updateScores}. Applications may determine the current default scorer with
- * {@link #getDefaultScorerPackage()} and request to change the default scorer by sending an
+ * {@link #getActiveScorerPackage()} and request to change the default scorer by sending an
  * {@link #ACTION_CHANGE_DEFAULT} broadcast with another scorer.
  *
  * @hide
@@ -81,38 +84,82 @@ public class NetworkScoreManager {
     public static final String EXTRA_NETWORKS_TO_SCORE = "networksToScore";
 
     private final Context mContext;
+    private final INetworkScoreService mService;
 
     /** @hide */
     public NetworkScoreManager(Context context) {
         mContext = context;
+        IBinder iBinder = ServiceManager.getService(Context.NETWORK_SCORE_SERVICE);
+        mService = INetworkScoreService.Stub.asInterface(iBinder);
     }
 
     /**
-     * Obtain the package name of the current default network scorer.
+     * Obtain the package name of the current active network scorer.
      *
-     * At any time, only one scorer application will receive {@link #ACTION_SCORE_NETWORKS}
+     * <p>At any time, only one scorer application will receive {@link #ACTION_SCORE_NETWORKS}
      * broadcasts and be allowed to call {@link #updateScores}. Applications may use this method to
      * determine the current scorer and offer the user the ability to select a different scorer via
      * the {@link #ACTION_CHANGE_DEFAULT} intent.
-     * @return the full package name of the current default scorer, or null if there is no active
+     * @return the full package name of the current active scorer, or null if there is no active
      *     scorer.
      */
-    public String getDefaultScorerPackage() {
-        // TODO: Implement.
-        return null;
+    public String getActiveScorerPackage() {
+        return NetworkScorerAppManager.getActiveScorer(mContext);
     }
 
     /**
      * Update network scores.
      *
-     * This may be called at any time to re-score active networks. Scores will generally be updated
-     * quickly, but if this method is called too frequently, the scores may be held and applied at
-     * a later time.
+     * <p>This may be called at any time to re-score active networks. Scores will generally be
+     * updated quickly, but if this method is called too frequently, the scores may be held and
+     * applied at a later time.
      *
      * @param networks the networks which have been scored by the scorer.
-     * @throws SecurityException if the caller is not the default scorer.
+     * @return whether the update was successful.
+     * @throws SecurityException if the caller is not the active scorer.
      */
-    public void updateScores(ScoredNetwork[] networks) throws SecurityException {
-        // TODO: Implement.
+    public boolean updateScores(ScoredNetwork[] networks) throws SecurityException {
+        try {
+            return mService.updateScores(networks);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Clear network scores.
+     *
+     * <p>Should be called when all scores need to be invalidated, i.e. because the scoring
+     * algorithm has changed and old scores can no longer be compared to future scores.
+     *
+     * <p>Note that scores will be cleared automatically when the active scorer changes, as scores
+     * from one scorer cannot be compared to those from another scorer.
+     *
+     * @return whether the clear was successful.
+     * @throws SecurityException if the caller is not the active scorer or privileged.
+     */
+    public boolean clearScores() throws SecurityException {
+        try {
+            return mService.clearScores();
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Set the active scorer to a new package and clear existing scores.
+     *
+     * @return true if the operation succeeded, or false if the new package is not a valid scorer.
+     * @throws SecurityException if the caller does not hold the
+     *      {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission indicating that
+     *      it can manage scorer applications.
+     * @hide
+     */
+    public boolean setActiveScorer(String packageName) throws SecurityException {
+        try {
+            return mService.setActiveScorer(packageName);
+        } catch (RemoteException e) {
+            return false;
+        }
     }
 }
@@ -26,6 +26,7 @@ import android.content.pm.ResolveInfo;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.text.TextUtils;
+import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -36,13 +37,14 @@ import java.util.List;
  *
  * @hide
  */
-public final class NetworkScorerApplication {
+public final class NetworkScorerAppManager {
+    private static final String TAG = "NetworkScorerAppManager";
 
     private static final Intent SCORE_INTENT =
             new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS);
 
     /** This class cannot be instantiated. */
-    private NetworkScorerApplication() {}
+    private NetworkScorerAppManager() {}
 
     /**
      * Returns the list of available scorer app package names.
@@ -111,30 +113,38 @@ public final class NetworkScorerApplication {
      * @param context the context of the calling application
      * @param packageName the packageName of the new scorer to use. If null, scoring will be
      *     disabled. Otherwise, the scorer will only be set if it is a valid scorer application.
+     * @return true if the scorer was changed, or false if the package is not a valid scorer.
      */
-    public static void setActiveScorer(Context context, String packageName) {
+    public static boolean setActiveScorer(Context context, String packageName) {
         String oldPackageName = Settings.Global.getString(context.getContentResolver(),
                 Settings.Global.NETWORK_SCORER_APP);
         if (TextUtils.equals(oldPackageName, packageName)) {
             // No change.
-            return;
+            return true;
         }
 
+        Log.i(TAG, "Changing network scorer from " + oldPackageName + " to " + packageName);
+
         if (packageName == null) {
             Settings.Global.putString(context.getContentResolver(), Global.NETWORK_SCORER_APP,
                     null);
+            return true;
         } else {
             // We only make the change if the new package is valid.
             Collection<String> applications = getAllValidScorers(context);
             if (isPackageValidScorer(applications, packageName)) {
                 Settings.Global.putString(context.getContentResolver(),
                         Settings.Global.NETWORK_SCORER_APP, packageName);
+                return true;
+            } else {
+                Log.w(TAG, "Requested network scorer is not valid: " + packageName);
+                return false;
             }
         }
     }
 
     /** Determine whether the application with the given UID is the enabled scorer. */
-    public static boolean isCallerDefaultScorer(Context context, int callingUid) {
+    public static boolean isCallerActiveScorer(Context context, int callingUid) {
         String defaultApp = getActiveScorer(context);
         if (defaultApp == null) {
             return false;
index 7af7998..33e81c2 100644 (file)
@@ -19,6 +19,9 @@ package android.net;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Arrays;
+import java.util.Objects;
+
 /**
  * A curve defining the network score over a range of RSSI values.
  *
@@ -94,6 +97,30 @@ public class RssiCurve implements Parcelable {
         out.writeByteArray(rssiBuckets);
     }
 
+    /**
+     * Determine if two RSSI curves are defined in the same way.
+     *
+     * <p>Note that two curves can be equivalent but defined differently, e.g. if one bucket in one
+     * curve is split into two buckets in another. For the purpose of this method, these curves are
+     * not considered equal to each other.
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        RssiCurve rssiCurve = (RssiCurve) o;
+
+        return start == rssiCurve.start &&
+                bucketWidth == rssiCurve.bucketWidth &&
+                Arrays.equals(rssiBuckets, rssiCurve.rssiBuckets);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(start, bucketWidth, rssiBuckets);
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
index 8af3c3c..7902313 100644 (file)
@@ -19,6 +19,8 @@ package android.net;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Objects;
+
 /**
  * A network identifier along with a score for the quality of that network.
  *
@@ -80,6 +82,22 @@ public class ScoredNetwork implements Parcelable {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        ScoredNetwork that = (ScoredNetwork) o;
+
+        return Objects.equals(networkKey, that.networkKey) &&
+                Objects.equals(rssiCurve, that.rssiCurve);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(networkKey, rssiCurve);
+    }
+
+    @Override
     public String toString() {
         return "ScoredNetwork[key=" + networkKey + ",score=" + rssiCurve + "]";
     }
index ffcd85a..9e92e89 100644 (file)
@@ -19,6 +19,7 @@ package android.net;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Objects;
 import java.util.regex.Pattern;
 
 /**
@@ -87,6 +88,21 @@ public class WifiKey implements Parcelable {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        WifiKey wifiKey = (WifiKey) o;
+
+        return Objects.equals(ssid, wifiKey.ssid) && Objects.equals(bssid, wifiKey.bssid);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(ssid, bssid);
+    }
+
+    @Override
     public String toString() {
         return "WifiKey[SSID=" + ssid + ",BSSID=" + bssid + "]";
     }
index 2df5dc1..c610146 100644 (file)
     <!-- default window inset isRound property -->
     <bool name="config_windowIsRound">false</bool>
 
+    <!-- Package name for default network scorer app; overridden by product overlays. -->
+    <string name="config_defaultNetworkScorerPackageName"></string>
 </resources>
index 03c617a..26efe36 100644 (file)
   <java-symbol type="bool" name="config_powerDecoupleAutoSuspendModeFromDisplay" />
   <java-symbol type="bool" name="config_powerDecoupleInteractiveModeFromDisplay" />
   <java-symbol type="string" name="config_customAdbPublicKeyConfirmationComponent" />
+  <java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
 
   <java-symbol type="layout" name="resolver_list" />
   <java-symbol type="id" name="resolver_list" />
@@ -33,7 +33,7 @@ import org.mockito.MockitoAnnotations;
 
 import java.util.Iterator;
 
-public class NetworkScorerApplicationTest extends InstrumentationTestCase {
+public class NetworkScorerAppManagerTest extends InstrumentationTestCase {
     @Mock private Context mMockContext;
     @Mock private PackageManager mMockPm;
 
@@ -64,7 +64,7 @@ public class NetworkScorerApplicationTest extends InstrumentationTestCase {
         setScorers(package1, package2, package3);
 
         Iterator<String> result =
-                NetworkScorerApplication.getAllValidScorers(mMockContext).iterator();
+                NetworkScorerAppManager.getAllValidScorers(mMockContext).iterator();
 
         assertTrue(result.hasNext());
         assertEquals("package1", result.next());
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
new file mode 100644 (file)
index 0000000..8a30e50
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2014 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 com.android.server;
+
+import android.Manifest.permission;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.net.INetworkScoreService;
+import android.net.NetworkKey;
+import android.net.NetworkScorerAppManager;
+import android.net.RssiCurve;
+import android.net.ScoredNetwork;
+import android.text.TextUtils;
+
+import com.android.internal.R;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Backing service for {@link android.net.NetworkScoreManager}.
+ * @hide
+ */
+public class NetworkScoreService extends INetworkScoreService.Stub {
+    private static final String TAG = "NetworkScoreService";
+
+    /** SharedPreference bit set to true after the service is first initialized. */
+    private static final String PREF_SCORING_PROVISIONED = "is_provisioned";
+
+    private final Context mContext;
+
+    // TODO: Delete this temporary class once we have a real place for scores.
+    private final Map<NetworkKey, RssiCurve> mScoredNetworks;
+
+    public NetworkScoreService(Context context) {
+        mContext = context;
+        mScoredNetworks = new HashMap<>();
+    }
+
+    /** Called when the system is ready to run third-party code but before it actually does so. */
+    void systemReady() {
+        SharedPreferences prefs = mContext.getSharedPreferences(TAG, Context.MODE_PRIVATE);
+        if (!prefs.getBoolean(PREF_SCORING_PROVISIONED, false)) {
+            // On first run, we try to initialize the scorer to the one configured at build time.
+            // This will be a no-op if the scorer isn't actually valid.
+            String defaultPackage = mContext.getResources().getString(
+                    R.string.config_defaultNetworkScorerPackageName);
+            if (!TextUtils.isEmpty(defaultPackage)) {
+                NetworkScorerAppManager.setActiveScorer(mContext, defaultPackage);
+            }
+            prefs.edit().putBoolean(PREF_SCORING_PROVISIONED, true).apply();
+        }
+    }
+
+    @Override
+    public boolean updateScores(ScoredNetwork[] networks) {
+        if (!NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid())) {
+            throw new SecurityException("Caller with UID " + getCallingUid() +
+                    " is not the active scorer.");
+        }
+
+        // TODO: Propagate these scores down to the network subsystem layer instead of just holding
+        // them in memory.
+        for (ScoredNetwork network : networks) {
+            mScoredNetworks.put(network.networkKey, network.rssiCurve);
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean clearScores() {
+        // Only the active scorer or the system (who can broadcast BROADCAST_SCORE_NETWORKS) should
+        // be allowed to flush all scores.
+        if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) ||
+                mContext.checkCallingOrSelfPermission(permission.BROADCAST_SCORE_NETWORKS) ==
+                        PackageManager.PERMISSION_GRANTED) {
+            clearInternal();
+            return true;
+        } else {
+            throw new SecurityException(
+                    "Caller is neither the active scorer nor the scorer manager.");
+        }
+    }
+
+    @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();
+        return NetworkScorerAppManager.setActiveScorer(mContext, packageName);
+    }
+
+    /** Clear scores. Callers are responsible for checking permissions as appropriate. */
+    private void clearInternal() {
+        // TODO: Propagate the flush down to the network subsystem layer.
+        mScoredNetworks.clear();
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
+        String currentScorer = NetworkScorerAppManager.getActiveScorer(mContext);
+        if (currentScorer == null) {
+            writer.println("Scoring is disabled.");
+            return;
+        }
+        writer.println("Current scorer: " + currentScorer);
+        if (mScoredNetworks.isEmpty()) {
+            writer.println("No networks scored.");
+        } else {
+            for (Map.Entry<NetworkKey, RssiCurve> entry : mScoredNetworks.entrySet()) {
+                writer.println(entry.getKey() + ": " + entry.getValue());
+            }
+        }
+    }
+}
index 912ac4d..f08d69f 100644 (file)
@@ -311,6 +311,7 @@ public final class SystemServer {
         NetworkStatsService networkStats = null;
         NetworkPolicyManagerService networkPolicy = null;
         ConnectivityService connectivity = null;
+        NetworkScoreService networkScore = null;
         NsdService serviceDiscovery= null;
         IPackageManager pm = null;
         WindowManagerService wm = null;
@@ -643,6 +644,14 @@ public final class SystemServer {
                 }
 
                 try {
+                    Slog.i(TAG, "Network Score Service");
+                    networkScore = new NetworkScoreService(context);
+                    ServiceManager.addService(Context.NETWORK_SCORE_SERVICE, networkScore);
+                } catch (Throwable e) {
+                    reportWtf("starting Network Score Service", e);
+                }
+
+                try {
                     Slog.i(TAG, "Network Service Discovery Service");
                     serviceDiscovery = NsdService.create(context);
                     ServiceManager.addService(
@@ -1021,6 +1030,7 @@ public final class SystemServer {
         final NetworkStatsService networkStatsF = networkStats;
         final NetworkPolicyManagerService networkPolicyF = networkPolicy;
         final ConnectivityService connectivityF = connectivity;
+        final NetworkScoreService networkScoreF = networkScore;
         final DockObserver dockF = dock;
         final WallpaperManagerService wallpaperF = wallpaper;
         final InputMethodManagerService immF = imm;
@@ -1069,6 +1079,11 @@ public final class SystemServer {
                     reportWtf("making Battery Service ready", e);
                 }
                 try {
+                    if (networkScoreF != null) networkScoreF.systemReady();
+                } catch (Throwable e) {
+                    reportWtf("making Network Score Service ready", e);
+                }
+                try {
                     if (networkManagementF != null) networkManagementF.systemReady();
                 } catch (Throwable e) {
                     reportWtf("making Network Managment Service ready", e);