Merge "VpnSettings stub unit tests"
diff --git a/src/com/android/settings/vpn2/AppManagementFragment.java b/src/com/android/settings/vpn2/AppManagementFragment.java
index b306577..d69d619 100644
--- a/src/com/android/settings/vpn2/AppManagementFragment.java
+++ b/src/com/android/settings/vpn2/AppManagementFragment.java
@@ -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.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 @@
     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 @@
         addPreferencesFromResource(R.xml.vpn_app_management);
 
         mPackageManager = getContext().getPackageManager();
-        mAppOpsManager = getContext().getSystemService(AppOpsManager.class);
         mConnectivityManager = getContext().getSystemService(ConnectivityManager.class);
 
         mPreferenceVersion = findPreference(KEY_VERSION);
@@ -199,19 +199,17 @@
                 isEnabled ? mPackageName : null, /* lockdownEnabled */ false);
     }
 
-    private boolean checkTargetVersion() {
-        if (mPackageInfo == null || mPackageInfo.applicationInfo == null) {
-            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 " + appInfo.packageName + " targets SDK version: " + targetSdk
+                        + "; must target at least " + Build.VERSION_CODES.N + " to use always-on.");
+            }
+            return false;
         }
-        final int targetSdk = mPackageInfo.applicationInfo.targetSdkVersion;
-        if (targetSdk >= Build.VERSION_CODES.N) {
-            return true;
-        }
-        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.");
-        }
-        return false;
+        return true;
     }
 
     private void updateUI() {
@@ -228,7 +226,7 @@
             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 @@
         }
 
         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 @@
             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 @@
         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() {
diff --git a/tests/unit/src/com/android/settings/vpn2/AppSettingsTest.java b/tests/unit/src/com/android/settings/vpn2/AppSettingsTest.java
new file mode 100644
index 0000000..fe437a6
--- /dev/null
+++ b/tests/unit/src/com/android/settings/vpn2/AppSettingsTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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;
+    }
+}