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

Commit 490849b3 authored by Robin Lee's avatar Robin Lee
Browse files

VpnSettings stub unit tests

For:
 - enforcing minimum target SDK
 - enforcing that only actual vpn apps are shown

Bug: 30355704
Change-Id: I4fcbea8ce0d0417c089a637d999ea83299cea5d0
parent 0dc7a13a
Loading
Loading
Loading
Loading
+27 −23
Original line number Diff line number Diff line
@@ -15,12 +15,14 @@
 */
package com.android.settings.vpn2;

import android.annotation.NonNull;
import android.app.AlertDialog;
import android.app.AppOpsManager;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -33,12 +35,13 @@ import android.provider.Settings;
import android.support.v7.preference.Preference;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.internal.net.VpnConfig;
import com.android.internal.util.ArrayUtils;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.RestrictedPreference;

@@ -57,13 +60,11 @@ public class AppManagementFragment extends SettingsPreferenceFragment
    private static final String KEY_ALWAYS_ON_VPN = "always_on_vpn";
    private static final String KEY_FORGET_VPN = "forget_vpn";

    private AppOpsManager mAppOpsManager;
    private PackageManager mPackageManager;
    private ConnectivityManager mConnectivityManager;

    // VPN app info
    private final int mUserId = UserHandle.myUserId();
    private int mPackageUid;
    private String mPackageName;
    private PackageInfo mPackageInfo;
    private String mVpnLabel;
@@ -105,7 +106,6 @@ public class AppManagementFragment extends SettingsPreferenceFragment
        addPreferencesFromResource(R.xml.vpn_app_management);

        mPackageManager = getContext().getPackageManager();
        mAppOpsManager = getContext().getSystemService(AppOpsManager.class);
        mConnectivityManager = getContext().getSystemService(ConnectivityManager.class);

        mPreferenceVersion = findPreference(KEY_VERSION);
@@ -199,20 +199,18 @@ public class AppManagementFragment extends SettingsPreferenceFragment
                isEnabled ? mPackageName : null, /* lockdownEnabled */ false);
    }

    private boolean checkTargetVersion() {
        if (mPackageInfo == null || mPackageInfo.applicationInfo == null) {
            return true;
        }
        final int targetSdk = mPackageInfo.applicationInfo.targetSdkVersion;
        if (targetSdk >= Build.VERSION_CODES.N) {
            return true;
        }
    @VisibleForTesting
    static boolean isAlwaysOnSupportedByApp(@NonNull ApplicationInfo appInfo) {
        final int targetSdk = appInfo.targetSdkVersion;
        if (targetSdk < Build.VERSION_CODES.N) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Package " + mPackageName + " targets SDK version " + targetSdk + "; must"
                    + " target at least " + Build.VERSION_CODES.N + " to use always-on.");
                Log.d(TAG, "Package " + appInfo.packageName + " targets SDK version: " + targetSdk
                        + "; must target at least " + Build.VERSION_CODES.N + " to use always-on.");
            }
            return false;
        }
        return true;
    }

    private void updateUI() {
        if (isAdded()) {
@@ -228,7 +226,7 @@ public class AppManagementFragment extends SettingsPreferenceFragment
            mPreferenceForget.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
                    mUserId);

            if (checkTargetVersion()) {
            if (isAlwaysOnSupportedByApp(mPackageInfo.applicationInfo)) {
                // setSummary doesn't override the admin message when user restriction is applied
                mPreferenceAlwaysOn.setSummary(null);
                // setEnabled is not required here, as checkRestrictionAndSetDisabled
@@ -266,7 +264,6 @@ public class AppManagementFragment extends SettingsPreferenceFragment
        }

        try {
            mPackageUid = mPackageManager.getPackageUid(mPackageName, /* PackageInfoFlags */ 0);
            mPackageInfo = mPackageManager.getPackageInfo(mPackageName, /* PackageInfoFlags */ 0);
            mVpnLabel = VpnConfig.getVpnLabel(getPrefContext(), mPackageName).toString();
        } catch (NameNotFoundException nnfe) {
@@ -274,7 +271,11 @@ public class AppManagementFragment extends SettingsPreferenceFragment
            return false;
        }

        if (!isVpnActivated()) {
        if (mPackageInfo.applicationInfo == null) {
            Log.e(TAG, "package does not include an application");
            return false;
        }
        if (!appHasVpnPermission(getContext(), mPackageInfo.applicationInfo)) {
            Log.e(TAG, "package didn't register VPN profile");
            return false;
        }
@@ -282,10 +283,13 @@ public class AppManagementFragment extends SettingsPreferenceFragment
        return true;
    }

    private boolean isVpnActivated() {
        final List<AppOpsManager.PackageOps> apps = mAppOpsManager.getOpsForPackage(mPackageUid,
                mPackageName, new int[]{OP_ACTIVATE_VPN});
        return apps != null && apps.size() > 0 && apps.get(0) != null;
    @VisibleForTesting
    static boolean appHasVpnPermission(Context context, @NonNull ApplicationInfo application) {
        final AppOpsManager service =
                (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        final List<AppOpsManager.PackageOps> ops = service.getOpsForPackage(application.uid,
                application.packageName, new int[]{OP_ACTIVATE_VPN});
        return !ArrayUtils.isEmpty(ops);
    }

    private boolean isLegacyVpnLockDownOrAnotherPackageAlwaysOn() {
+104 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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.settings.vpn2;

import static com.android.settings.vpn2.AppManagementFragment.isAlwaysOnSupportedByApp;
import static com.android.settings.vpn2.AppManagementFragment.appHasVpnPermission;
import static org.mockito.Mockito.*;

import android.app.AppOpsManager;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.os.Process;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.content.Context;

import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

public class AppSettingsTest extends AndroidTestCase {
    private static final String TAG = AppSettingsTest.class.getSimpleName();

    @Mock private Context mContext;
    @Mock private AppOpsManager mAppOps;

    @Override
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
    }

    @SmallTest
    public void testAlwaysOnVersionRestriction() {
        ApplicationInfo mockApp = createMockApp();

        // API 23 (MNC) = not supported
        mockApp.targetSdkVersion = Build.VERSION_CODES.M;
        assertFalse(isAlwaysOnSupportedByApp(mockApp));

        // API 24 (NYC) = supported
        mockApp.targetSdkVersion = Build.VERSION_CODES.N;
        assertTrue(isAlwaysOnSupportedByApp(mockApp));

        // API 25 (NYC MR1) = supported
        mockApp.targetSdkVersion = Build.VERSION_CODES.N_MR1;
        assertTrue(isAlwaysOnSupportedByApp(mockApp));
    }

    @SmallTest
    public void testAppOpsRequiredToOpenFragment() {
        ApplicationInfo mockApp = createMockApp();

        final AppOpsManager.PackageOps[] blankOps = {
            new AppOpsManager.PackageOps(mockApp.packageName, mockApp.uid, new ArrayList<>()),
            new AppOpsManager.PackageOps(mockApp.packageName, mockApp.uid, new ArrayList<>())
        };

        // List with one package op
        when(mAppOps.getOpsForPackage(eq(mockApp.uid), eq(mockApp.packageName), any()))
                .thenReturn(Arrays.asList(new AppOpsManager.PackageOps[] {blankOps[0]}));
        assertTrue(appHasVpnPermission(mContext, mockApp));

        // List with more than one package op
        when(mAppOps.getOpsForPackage(eq(mockApp.uid), eq(mockApp.packageName), any()))
                .thenReturn(Arrays.asList(blankOps));
        assertTrue(appHasVpnPermission(mContext, mockApp));

        // Empty list
        when(mAppOps.getOpsForPackage(eq(mockApp.uid), eq(mockApp.packageName), any()))
                .thenReturn(Collections.emptyList());
        assertFalse(appHasVpnPermission(mContext, mockApp));

        // Null list (may be returned in place of an empty list)
        when(mAppOps.getOpsForPackage(eq(mockApp.uid), eq(mockApp.packageName), any()))
                .thenReturn(null);
        assertFalse(appHasVpnPermission(mContext, mockApp));
    }

    private static ApplicationInfo createMockApp() {
        final ApplicationInfo app = new ApplicationInfo();
        app.packageName = "com.example.mockvpn";
        app.uid = Process.FIRST_APPLICATION_UID;
        return app;
    }
}