Loading src/com/android/settings/vpn2/AppManagementFragment.java +27 −23 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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()) { Loading @@ -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 Loading Loading @@ -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) { Loading @@ -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; } Loading @@ -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() { Loading tests/unit/src/com/android/settings/vpn2/AppSettingsTest.java 0 → 100644 +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; } } Loading
src/com/android/settings/vpn2/AppManagementFragment.java +27 −23 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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()) { Loading @@ -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 Loading Loading @@ -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) { Loading @@ -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; } Loading @@ -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() { Loading
tests/unit/src/com/android/settings/vpn2/AppSettingsTest.java 0 → 100644 +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; } }