VpnSettings PreferenceList tests

For validating that when VPNs are added / removed, the right set of
changes are made to the PreferenceGroup in which they are supposed to
be shown.

Bug: 30998549
Bug: 29093779
Test: runtest -x packages/apps/Settings/tests/unit/src/com/android/settings/vpn2/PreferenceListTest.java
Change-Id: I9394db0e78cc984ab62e3670aa0a7942ae767a66
This commit is contained in:
Robin Lee
2016-11-24 15:46:54 +00:00
parent 4f0a0d0a40
commit 9c2758f407
2 changed files with 279 additions and 70 deletions

View File

@@ -50,6 +50,7 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
@@ -63,6 +64,7 @@ import com.android.settingslib.RestrictedLockUtils;
import com.google.android.collect.Lists;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -221,81 +223,129 @@ public class VpnSettings extends RestrictedSettingsFragment implements
final Set<AppVpnInfo> alwaysOnAppVpnInfos = getAlwaysOnAppVpnInfos();
final String lockdownVpnKey = VpnUtils.getLockdownVpn();
// Refresh list of VPNs
getActivity().runOnUiThread(new UpdatePreferences(this)
.legacyVpns(vpnProfiles, connectedLegacyVpns, lockdownVpnKey)
.appVpns(vpnApps, connectedAppVpns, alwaysOnAppVpnInfos));
synchronized (this) {
if (mUpdater != null) {
mUpdater.removeMessages(RESCAN_MESSAGE);
mUpdater.sendEmptyMessageDelayed(RESCAN_MESSAGE, RESCAN_INTERVAL_MS);
}
}
// Refresh list of VPNs
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Can't do anything useful if the context has gone away
if (!isAdded()) {
return;
}
// Find new VPNs by subtracting existing ones from the full set
final Set<Preference> updates = new ArraySet<>();
// Add legacy VPNs
for (VpnProfile profile : vpnProfiles) {
LegacyVpnPreference p = findOrCreatePreference(profile, true);
if (connectedLegacyVpns.containsKey(profile.key)) {
p.setState(connectedLegacyVpns.get(profile.key).state);
} else {
p.setState(LegacyVpnPreference.STATE_NONE);
}
p.setAlwaysOn(lockdownVpnKey != null && lockdownVpnKey.equals(profile.key));
updates.add(p);
}
// Show connected VPNs even if the original entry in keystore is gone
for (LegacyVpnInfo vpn : connectedLegacyVpns.values()) {
final VpnProfile stubProfile = new VpnProfile(vpn.key);
LegacyVpnPreference p = findOrCreatePreference(stubProfile, false);
p.setState(vpn.state);
p.setAlwaysOn(lockdownVpnKey != null && lockdownVpnKey.equals(vpn.key));
updates.add(p);
}
// Add VpnService VPNs
for (AppVpnInfo app : vpnApps) {
AppPreference p = findOrCreatePreference(app);
if (connectedAppVpns.contains(app)) {
p.setState(AppPreference.STATE_CONNECTED);
} else {
p.setState(AppPreference.STATE_DISCONNECTED);
}
p.setAlwaysOn(alwaysOnAppVpnInfos.contains(app));
updates.add(p);
}
// Trim out deleted VPN preferences
mLegacyVpnPreferences.values().retainAll(updates);
mAppPreferences.values().retainAll(updates);
final PreferenceGroup vpnGroup = getPreferenceScreen();
for (int i = vpnGroup.getPreferenceCount() - 1; i >= 0; i--) {
Preference p = vpnGroup.getPreference(i);
if (updates.contains(p)) {
updates.remove(p);
} else {
vpnGroup.removePreference(p);
}
}
// Show any new preferences on the screen
for (Preference pref : updates) {
vpnGroup.addPreference(pref);
}
}
});
return true;
}
@VisibleForTesting
static class UpdatePreferences implements Runnable {
private List<VpnProfile> vpnProfiles = Collections.<VpnProfile>emptyList();
private List<AppVpnInfo> vpnApps = Collections.<AppVpnInfo>emptyList();
private Map<String, LegacyVpnInfo> connectedLegacyVpns =
Collections.<String, LegacyVpnInfo>emptyMap();
private Set<AppVpnInfo> connectedAppVpns = Collections.<AppVpnInfo>emptySet();
private Set<AppVpnInfo> alwaysOnAppVpnInfos = Collections.<AppVpnInfo>emptySet();
private String lockdownVpnKey = null;
private final VpnSettings mSettings;
public UpdatePreferences(VpnSettings settings) {
mSettings = settings;
}
public final UpdatePreferences legacyVpns(List<VpnProfile> vpnProfiles,
Map<String, LegacyVpnInfo> connectedLegacyVpns, String lockdownVpnKey) {
this.vpnProfiles = vpnProfiles;
this.connectedLegacyVpns = connectedLegacyVpns;
this.lockdownVpnKey = lockdownVpnKey;
return this;
}
public final UpdatePreferences appVpns(List<AppVpnInfo> vpnApps,
Set<AppVpnInfo> connectedAppVpns, Set<AppVpnInfo> alwaysOnAppVpnInfos) {
this.vpnApps = vpnApps;
this.connectedAppVpns = connectedAppVpns;
this.alwaysOnAppVpnInfos = alwaysOnAppVpnInfos;
return this;
}
@Override @UiThread
public void run() {
if (!mSettings.canAddPreferences()) {
return;
}
// Find new VPNs by subtracting existing ones from the full set
final Set<Preference> updates = new ArraySet<>();
// Add legacy VPNs
for (VpnProfile profile : vpnProfiles) {
LegacyVpnPreference p = mSettings.findOrCreatePreference(profile, true);
if (connectedLegacyVpns.containsKey(profile.key)) {
p.setState(connectedLegacyVpns.get(profile.key).state);
} else {
p.setState(LegacyVpnPreference.STATE_NONE);
}
p.setAlwaysOn(lockdownVpnKey != null && lockdownVpnKey.equals(profile.key));
updates.add(p);
}
// Show connected VPNs even if the original entry in keystore is gone
for (LegacyVpnInfo vpn : connectedLegacyVpns.values()) {
final VpnProfile stubProfile = new VpnProfile(vpn.key);
LegacyVpnPreference p = mSettings.findOrCreatePreference(stubProfile, false);
p.setState(vpn.state);
p.setAlwaysOn(lockdownVpnKey != null && lockdownVpnKey.equals(vpn.key));
updates.add(p);
}
// Add VpnService VPNs
for (AppVpnInfo app : vpnApps) {
AppPreference p = mSettings.findOrCreatePreference(app);
if (connectedAppVpns.contains(app)) {
p.setState(AppPreference.STATE_CONNECTED);
} else {
p.setState(AppPreference.STATE_DISCONNECTED);
}
p.setAlwaysOn(alwaysOnAppVpnInfos.contains(app));
updates.add(p);
}
// Trim out deleted VPN preferences
mSettings.setShownPreferences(updates);
}
}
@VisibleForTesting
public boolean canAddPreferences() {
return isAdded();
}
@VisibleForTesting @UiThread
public void setShownPreferences(final Collection<Preference> updates) {
mLegacyVpnPreferences.values().retainAll(updates);
mAppPreferences.values().retainAll(updates);
// Change {@param updates} in-place to only contain new preferences that were not already
// added to the preference screen.
final PreferenceGroup vpnGroup = getPreferenceScreen();
for (int i = vpnGroup.getPreferenceCount() - 1; i >= 0; i--) {
Preference p = vpnGroup.getPreference(i);
if (updates.contains(p)) {
updates.remove(p);
} else {
vpnGroup.removePreference(p);
}
}
// Show any new preferences on the screen
for (Preference pref : updates) {
vpnGroup.addPreference(pref);
}
}
@Override
public boolean onPreferenceClick(Preference preference) {
if (preference instanceof LegacyVpnPreference) {
@@ -375,8 +425,8 @@ public class VpnSettings extends RestrictedSettingsFragment implements
}
};
@UiThread
private LegacyVpnPreference findOrCreatePreference(VpnProfile profile, boolean update) {
@VisibleForTesting @UiThread
public LegacyVpnPreference findOrCreatePreference(VpnProfile profile, boolean update) {
LegacyVpnPreference pref = mLegacyVpnPreferences.get(profile.key);
boolean created = false;
if (pref == null ) {
@@ -393,8 +443,8 @@ public class VpnSettings extends RestrictedSettingsFragment implements
return pref;
}
@UiThread
private AppPreference findOrCreatePreference(AppVpnInfo app) {
@VisibleForTesting @UiThread
public AppPreference findOrCreatePreference(AppVpnInfo app) {
AppPreference pref = mAppPreferences.get(app);
if (pref == null) {
pref = new AppPreference(getPrefContext(), app.userId, app.packageName);

View File

@@ -0,0 +1,159 @@
/*
* 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 org.mockito.AdditionalMatchers.not;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import android.content.Context;
import android.content.Context;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.text.TextUtils;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnProfile;
import com.android.settings.R;
import com.android.settings.vpn2.VpnSettings;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
public class PreferenceListTest extends AndroidTestCase {
private static final String TAG = "PreferenceListTest";
@Mock VpnSettings mSettings;
final Map<String, LegacyVpnPreference> mLegacyMocks = new HashMap<>();
final Map<AppVpnInfo, AppPreference> mAppMocks = new HashMap<>();
@Override
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mLegacyMocks.clear();
mAppMocks.clear();
doAnswer(invocation -> {
final String key = ((VpnProfile)(invocation.getArguments()[0])).key;
if (!mLegacyMocks.containsKey(key)) {
mLegacyMocks.put(key, mock(LegacyVpnPreference.class));
}
return mLegacyMocks.get(key);
}).when(mSettings).findOrCreatePreference(any(VpnProfile.class), anyBoolean());
doAnswer(invocation -> {
final AppVpnInfo key = (AppVpnInfo)(invocation.getArguments()[0]);
if (!mAppMocks.containsKey(key)) {
mAppMocks.put(key, mock(AppPreference.class));
}
return mAppMocks.get(key);
}).when(mSettings).findOrCreatePreference(any(AppVpnInfo.class));
doNothing().when(mSettings).setShownPreferences(any());
doReturn(true).when(mSettings).canAddPreferences();
}
@SmallTest
public void testNothingShownByDefault() {
final VpnSettings.UpdatePreferences updater = new VpnSettings.UpdatePreferences(mSettings);
updater.run();
verify(mSettings, never()).findOrCreatePreference(any(VpnProfile.class), anyBoolean());
assertEquals(0, mLegacyMocks.size());
assertEquals(0, mAppMocks.size());
}
@SmallTest
public void testDisconnectedLegacyVpnShown() {
final VpnProfile vpnProfile = new VpnProfile("test-disconnected");
final VpnSettings.UpdatePreferences updater = new VpnSettings.UpdatePreferences(mSettings);
updater.legacyVpns(
/* vpnProfiles */ Collections.<VpnProfile>singletonList(vpnProfile),
/* connectedLegacyVpns */ Collections.<String, LegacyVpnInfo>emptyMap(),
/* lockdownVpnKey */ null);
updater.run();
verify(mSettings, times(1)).findOrCreatePreference(any(VpnProfile.class), eq(true));
assertEquals(1, mLegacyMocks.size());
assertEquals(0, mAppMocks.size());
}
@SmallTest
public void testConnectedLegacyVpnShownIfDeleted() {
final LegacyVpnInfo connectedLegacyVpn =new LegacyVpnInfo();
connectedLegacyVpn.key = "test-connected";
final VpnSettings.UpdatePreferences updater = new VpnSettings.UpdatePreferences(mSettings);
updater.legacyVpns(
/* vpnProfiles */ Collections.<VpnProfile>emptyList(),
/* connectedLegacyVpns */ new HashMap<String, LegacyVpnInfo>() {{
put(connectedLegacyVpn.key, connectedLegacyVpn);
}},
/* lockdownVpnKey */ null);
updater.run();
verify(mSettings, times(1)).findOrCreatePreference(any(VpnProfile.class), eq(false));
assertEquals(1, mLegacyMocks.size());
assertEquals(0, mAppMocks.size());
}
@SmallTest
public void testConnectedLegacyVpnShownExactlyOnce() {
final VpnProfile vpnProfile = new VpnProfile("test-no-duplicates");
final LegacyVpnInfo connectedLegacyVpn = new LegacyVpnInfo();
connectedLegacyVpn.key = new String(vpnProfile.key);
final VpnSettings.UpdatePreferences updater = new VpnSettings.UpdatePreferences(mSettings);
updater.legacyVpns(
/* vpnProfiles */ Collections.<VpnProfile>singletonList(vpnProfile),
/* connectedLegacyVpns */ new HashMap<String, LegacyVpnInfo>() {{
put(connectedLegacyVpn.key, connectedLegacyVpn);
}},
/* lockdownVpnKey */ null);
updater.run();
final ArgumentMatcher<VpnProfile> equalsFake = new ArgumentMatcher<VpnProfile>() {
@Override
public boolean matches(final Object arg) {
if (arg == vpnProfile) return true;
if (arg == null) return false;
return TextUtils.equals(((VpnProfile) arg).key, vpnProfile.key);
}
};
// The VPN profile should have been used to create a preference and set up at laest once
// with update=true to fill in all the fields.
verify(mSettings, atLeast(1)).findOrCreatePreference(argThat(equalsFake), eq(true));
// ...But no other VPN profile key should ever have been passed in.
verify(mSettings, never()).findOrCreatePreference(not(argThat(equalsFake)), anyBoolean());
// And so we should still have exactly 1 preference created.
assertEquals(1, mLegacyMocks.size());
assertEquals(0, mAppMocks.size());
}
}