diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/java/android/net/NetworkScorerApplication.java | 156 | ||||
-rw-r--r-- | core/java/android/provider/Settings.java | 7 | ||||
-rw-r--r-- | core/tests/coretests/Android.mk | 2 | ||||
-rw-r--r-- | core/tests/coretests/src/android/net/NetworkScorerApplicationTest.java | 101 |
4 files changed, 265 insertions, 1 deletions
diff --git a/core/java/android/net/NetworkScorerApplication.java b/core/java/android/net/NetworkScorerApplication.java new file mode 100644 index 0000000..b137ad3 --- /dev/null +++ b/core/java/android/net/NetworkScorerApplication.java @@ -0,0 +1,156 @@ +/* + * 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.Manifest.permission; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.provider.Settings; +import android.provider.Settings.Global; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Internal class for managing the primary network scorer application. + * + * @hide + */ +public final class NetworkScorerApplication { + + private static final Intent SCORE_INTENT = + new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS); + + /** This class cannot be instantiated. */ + private NetworkScorerApplication() {} + + /** + * Returns the list of available scorer app package names. + * + * <p>A network scorer is any application which: + * <ul> + * <li>Declares the {@link android.Manifest.permission#SCORE_NETWORKS} permission. + * <li>Includes a receiver for {@link NetworkScoreManager#ACTION_SCORE_NETWORKS} guarded by the + * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission. + * </ul> + * + * @return the list of scorers, or the empty list if there are no valid scorers. + */ + public static Collection<String> getAllValidScorers(Context context) { + List<String> scorers = new ArrayList<>(); + + PackageManager pm = context.getPackageManager(); + List<ResolveInfo> receivers = pm.queryBroadcastReceivers(SCORE_INTENT, 0 /* flags */); + for (ResolveInfo receiver : receivers) { + // This field is a misnomer, see android.content.pm.ResolveInfo#activityInfo + final ActivityInfo receiverInfo = receiver.activityInfo; + if (receiverInfo == null) { + // Should never happen with queryBroadcastReceivers, but invalid nonetheless. + continue; + } + if (!permission.BROADCAST_SCORE_NETWORKS.equals(receiverInfo.permission)) { + // Receiver doesn't require the BROADCAST_SCORE_NETWORKS permission, which means + // anyone could trigger network scoring and flood the framework with score requests. + continue; + } + if (pm.checkPermission(permission.SCORE_NETWORKS, receiverInfo.packageName) != + PackageManager.PERMISSION_GRANTED) { + // Application doesn't hold the SCORE_NETWORKS permission, so the user never + // approved it as a network scorer. + continue; + } + scorers.add(receiverInfo.packageName); + } + + return scorers; + } + + /** + * Get the application package name to use for scoring networks. + * + * @return the scorer package or null if scoring is disabled (including if no scorer was ever + * selected) or if the previously-set scorer is no longer a valid scorer app (e.g. because + * it was disabled or uninstalled). + */ + public static String getActiveScorer(Context context) { + String scorerPackage = Settings.Global.getString(context.getContentResolver(), + Global.NETWORK_SCORER_APP); + Collection<String> applications = getAllValidScorers(context); + if (isPackageValidScorer(applications, scorerPackage)) { + return scorerPackage; + } else { + return null; + } + } + + /** + * Set the specified package as the default scorer application. + * + * <p>The caller must have permission to write to {@link Settings.Global}. + * + * @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. + */ + public static void 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; + } + + if (packageName == null) { + Settings.Global.putString(context.getContentResolver(), Global.NETWORK_SCORER_APP, + null); + } 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); + } + } + } + + /** Determine whether the application with the given UID is the enabled scorer. */ + public static boolean isCallerDefaultScorer(Context context, int callingUid) { + String defaultApp = getActiveScorer(context); + if (defaultApp == null) { + return false; + } + AppOpsManager appOpsMgr = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + try { + appOpsMgr.checkPackage(callingUid, defaultApp); + return true; + } catch (SecurityException e) { + return false; + } + } + + /** Returns true if the given package is a valid scorer. */ + private static boolean isPackageValidScorer(Collection<String> scorerPackageNames, + String packageName) { + return packageName != null && scorerPackageNames.contains(packageName); + } +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 7062933..1e202ca 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5097,6 +5097,13 @@ public final class Settings { public static final String NETWORK_PREFERENCE = "network_preference"; /** + * Which package name to use for network scoring. If null, or if the package is not a valid + * scorer app, external network scores will neither be requested nor accepted. + * @hide + */ + public static final String NETWORK_SCORER_APP = "network_scorer_app"; + + /** * If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment * to SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been * exceeded. diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk index 73a53cb..6bdeaf0 100644 --- a/core/tests/coretests/Android.mk +++ b/core/tests/coretests/Android.mk @@ -23,7 +23,7 @@ LOCAL_SRC_FILES := \ LOCAL_DX_FLAGS := --core-library LOCAL_AAPT_FLAGS = -0 dat -0 gld -LOCAL_STATIC_JAVA_LIBRARIES := core-tests-support android-common frameworks-core-util-lib mockwebserver guava littlemock +LOCAL_STATIC_JAVA_LIBRARIES := core-tests-support android-common frameworks-core-util-lib mockwebserver guava littlemock mockito-target LOCAL_JAVA_LIBRARIES := android.test.runner conscrypt telephony-common LOCAL_PACKAGE_NAME := FrameworksCoreTests diff --git a/core/tests/coretests/src/android/net/NetworkScorerApplicationTest.java b/core/tests/coretests/src/android/net/NetworkScorerApplicationTest.java new file mode 100644 index 0000000..6d5ede8 --- /dev/null +++ b/core/tests/coretests/src/android/net/NetworkScorerApplicationTest.java @@ -0,0 +1,101 @@ +/* + * 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.Manifest.permission; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.test.InstrumentationTestCase; + +import com.google.android.collect.Lists; + +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.Iterator; + +public class NetworkScorerApplicationTest extends InstrumentationTestCase { + @Mock private Context mMockContext; + @Mock private PackageManager mMockPm; + + @Override + public void setUp() throws Exception { + super.setUp(); + + // Configuration needed to make mockito/dexcache work. + System.setProperty("dexmaker.dexcache", + getInstrumentation().getTargetContext().getCacheDir().getPath()); + ClassLoader newClassLoader = getInstrumentation().getClass().getClassLoader(); + Thread.currentThread().setContextClassLoader(newClassLoader); + + MockitoAnnotations.initMocks(this); + Mockito.when(mMockContext.getPackageManager()).thenReturn(mMockPm); + } + + public void testGetAllValidScorers() throws Exception { + // Package 1 - Valid scorer. + ResolveInfo package1 = buildResolveInfo("package1", true, true); + + // Package 2 - Receiver does not have BROADCAST_SCORE_NETWORKS permission. + ResolveInfo package2 = buildResolveInfo("package2", false, true); + + // Package 3 - App does not have SCORE_NETWORKS permission. + ResolveInfo package3 = buildResolveInfo("package3", true, false); + + setScorers(package1, package2, package3); + + Iterator<String> result = + NetworkScorerApplication.getAllValidScorers(mMockContext).iterator(); + + assertTrue(result.hasNext()); + assertEquals("package1", result.next()); + + assertFalse(result.hasNext()); + } + + private void setScorers(ResolveInfo... scorers) { + Mockito.when(mMockPm.queryBroadcastReceivers( + Mockito.argThat(new ArgumentMatcher<Intent>() { + @Override + public boolean matches(Object object) { + Intent intent = (Intent) object; + return NetworkScoreManager.ACTION_SCORE_NETWORKS.equals(intent.getAction()); + } + }), Mockito.eq(0))) + .thenReturn(Lists.newArrayList(scorers)); + } + + private ResolveInfo buildResolveInfo(String packageName, + boolean hasReceiverPermission, boolean hasScorePermission) throws Exception { + Mockito.when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName)) + .thenReturn(hasScorePermission ? + PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED); + + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = new ActivityInfo(); + resolveInfo.activityInfo.packageName = packageName; + if (hasReceiverPermission) { + resolveInfo.activityInfo.permission = permission.BROADCAST_SCORE_NETWORKS; + } + return resolveInfo; + } +} |