Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4c37b79c authored by Jeff Davidson's avatar Jeff Davidson Committed by Android (Google) Code Review
Browse files

Merge "Class for managing the active scorer application."

parents 047f3c71 dd6fd1e6
Loading
Loading
Loading
Loading
+156 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -5096,6 +5096,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
+1 −1
Original line number Diff line number Diff line
@@ -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

+101 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}