Change build targets for settings tests

1. Split existing tests into fully automatable (unit/) and not fully
automatable (app/)
2. Revert previous changes to test runners on app/
3. Fix some typos in readme

This will enable us to add the fully automatable test target
SettingsUnitTests to the continuous integration tests on the platform.

Change-Id: I0f42c59fde84891e4bfc379150c2f0c61ce1329f
This commit is contained in:
Benjamin Franz
2016-01-21 12:27:36 +00:00
parent 11805d159c
commit d11b713a9f
104 changed files with 98 additions and 37 deletions

View File

@@ -0,0 +1,134 @@
/*
* Copyright (C) 2010 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;
import com.android.settings.Settings;
import com.android.settings.tests.Manufacturer;
import com.android.settings.tests.Operator;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.test.ActivityInstrumentationTestCase2;
import java.util.List;
/**
* Tests for the Settings operator/manufacturer hook.
*
* Running all tests:
*
* make SettingsTests
* adb push SettingsTests.apk /system/app/SettingsTests.apk
* adb shell am instrument \
* -w com.android.settings.tests/android.test.InstrumentationTestRunner
*/
public class SettingsHookTests extends ActivityInstrumentationTestCase2<Settings> {
private static final String PACKAGE_NAME = "com.android.settings.tests";
private static final String KEY_SETTINGS_ROOT = "parent";
private static final String KEY_SETTINGS_OPERATOR = "operator_settings";
private static final String KEY_SETTINGS_MANUFACTURER = "manufacturer_settings";
private static final String INTENT_OPERATOR_HOOK = "com.android.settings.OPERATOR_APPLICATION_SETTING";
private static final String INTENT_MANUFACTURER_HOOK = "com.android.settings.MANUFACTURER_APPLICATION_SETTING";
private Settings mSettings;
public SettingsHookTests() {
super("com.android.settings", Settings.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
mSettings = getActivity();
}
/**
* Test that the operator/manufacturer settings hook test application is
* available and that it's installed in the device's system image.
*/
public void testSettingsHookTestAppAvailable() throws Exception {
Context context = mSettings.getApplicationContext();
PackageManager pm = context.getPackageManager();
ApplicationInfo applicationInfo = pm.getApplicationInfo(PACKAGE_NAME, 0);
assertTrue((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
}
/**
* Test that the operator test activity has registered an intent-filter for
* an action named 'android.settings.OPERATOR_APPLICATION_SETTING'.
*/
public void testOperatorIntentFilter() {
boolean result = false;
Context context = mSettings.getApplicationContext();
PackageManager pm = context.getPackageManager();
Intent intent = new Intent(INTENT_OPERATOR_HOOK);
List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
for (ResolveInfo resolveInfo : list) {
if (resolveInfo.activityInfo.packageName.equals(PACKAGE_NAME)) {
result = true;
}
}
assertTrue("Intent-filter not found", result);
}
/**
* Test that the manufacturer test activity has registered an intent-filter
* for an action named 'android.settings.MANUFACTURER_APPLICATION_SETTING'.
*/
public void testManufacturerIntentFilter() {
boolean result = false;
Context context = mSettings.getApplicationContext();
PackageManager pm = context.getPackageManager();
Intent intent = new Intent(INTENT_MANUFACTURER_HOOK);
List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
for (ResolveInfo resolveInfo : list) {
if (resolveInfo.activityInfo.packageName.equals(PACKAGE_NAME)) {
result = true;
}
}
assertTrue("Intent-filter not found", result);
}
/**
* Test that the operator preference is available in the Settings
* application.
*/
public void testOperatorPreferenceAvailable() {
// TODO: fix this test case to work with fragments
// PreferenceGroup root = (PreferenceGroup)mSettings.findPreference(KEY_SETTINGS_ROOT);
// Preference operatorPreference = root.findPreference(KEY_SETTINGS_OPERATOR);
// assertNotNull(operatorPreference);
}
/**
* Test that the manufacturer preference is available in the Settings
* application.
*/
public void testManufacturerPreferenceAvailable() {
// TODO: fix this test case to work with fragments
// PreferenceGroup root = (PreferenceGroup)mSettings.findPreference(KEY_SETTINGS_ROOT);
// Preference manufacturerHook = root.findPreference(KEY_SETTINGS_MANUFACTURER);
// assertNotNull(manufacturerHook);
}
}

View File

@@ -0,0 +1,177 @@
/*
* Copyright (C) 2009 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.tests;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
public class BluetoothRequestPermissionTest extends Activity {
private static final String TAG = "BluetoothRequestPermissionTest";
BluetoothAdapter mAdapter;
private ArrayAdapter<String> mMsgAdapter;
// Discoverable button alternates between 20 second timeout and no timeout.
private boolean mDiscoveryWithTimeout = true;
private class BtOnClickListener implements OnClickListener {
final boolean mEnableOnly; // enable or enable + discoverable
public BtOnClickListener(boolean enableOnly) {
mEnableOnly = enableOnly;
}
public void onClick(View v) {
requestPermission(mEnableOnly);
}
}
private class BtScanOnClickListener implements OnClickListener {
public void onClick(View v) {
Button scanButton = (Button) v;
if (mAdapter.isDiscovering()) {
mAdapter.cancelDiscovery();
scanButton.setText(R.string.start_scan);
} else {
mAdapter.startDiscovery();
scanButton.setText(R.string.stop_scan);
}
}
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.bluetooth_request_permission_test);
mAdapter = BluetoothAdapter.getDefaultAdapter();
Button enable = (Button) findViewById(R.id.enable);
enable.setOnClickListener(new BtOnClickListener(true /* enable */));
Button discoverable = (Button) findViewById(R.id.discoverable);
discoverable.setOnClickListener(new BtOnClickListener(false /* enable & discoverable */));
Button scanButton = (Button) findViewById(R.id.scan);
scanButton.setOnClickListener(new BtScanOnClickListener());
if (mAdapter.isDiscovering()) {
scanButton.setText(R.string.stop_scan);
} else {
scanButton.setText(R.string.start_scan);
}
mMsgAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
ListView listView = (ListView) findViewById(R.id.msg_container);
listView.setAdapter(mMsgAdapter);
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
addMsg("Initialized");
}
void requestPermission(boolean enableOnly) {
Intent i = new Intent();
if (enableOnly) {
addMsg("Starting activity to enable bt");
i.setAction(BluetoothAdapter.ACTION_REQUEST_ENABLE);
} else {
addMsg("Starting activity to enable bt + discovery");
i.setAction(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
// Discoverability duration toggles between 20 seconds and no timeout.
int timeout = (mDiscoveryWithTimeout ? 20 : 0);
i.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, timeout);
mDiscoveryWithTimeout = !mDiscoveryWithTimeout;
}
startActivityForResult(i, 1);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode != 1) {
Log.e(TAG, "Unexpected onActivityResult " + requestCode + " " + resultCode);
return;
}
if (resultCode == Activity.RESULT_CANCELED) {
addMsg("Result = RESULT_CANCELED");
} else if (resultCode == Activity.RESULT_OK) {
addMsg("Result = RESULT_OK (not expected for discovery)");
} else {
addMsg("Result = " + resultCode);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(mReceiver);
}
private void addMsg(String msg) {
mMsgAdapter.add(msg);
Log.d(TAG, "msg");
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null)
return;
String action = intent.getAction();
if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
String stateStr = "???";
switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothDevice.ERROR)) {
case BluetoothAdapter.STATE_OFF:
stateStr = "off";
break;
case BluetoothAdapter.STATE_TURNING_ON:
stateStr = "turning on";
break;
case BluetoothAdapter.STATE_ON:
stateStr = "on";
break;
case BluetoothAdapter.STATE_TURNING_OFF:
stateStr = "turning off";
break;
}
addMsg("Bluetooth status = " + stateStr);
} else if (action.equals(BluetoothDevice.ACTION_FOUND)) {
String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
addMsg("Found: " + name);
} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED)) {
addMsg("Scan started...");
} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
addMsg("Scan ended");
}
}
};
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2010 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.tests;
import android.app.Activity;
import android.os.Bundle;
public class Manufacturer extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.manufacturer_main);
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2010 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.tests;
import android.app.Activity;
import android.os.Bundle;
public class Operator extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.operator_main);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2007 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.tests;
import android.app.Activity;
import android.test.LaunchPerformanceBase;
import android.os.Bundle;
import java.util.Map;
/**
* Instrumentation class for Settings launch performance testing.
*/
public class SettingsLaunchPerformance extends LaunchPerformanceBase {
public static final String LOG_TAG = "SettingsLaunchPerformance";
public SettingsLaunchPerformance() {
super();
}
@Override
public void onCreate(Bundle arguments) {
super.onCreate(arguments);
mIntent.setClassName(getTargetContext(), "com.android.settings.Settings");
start();
}
/**
* Calls LaunchApp and finish.
*/
@Override
public void onStart() {
super.onStart();
LaunchApp();
finish(Activity.RESULT_OK, mResults);
}
}

View File

@@ -0,0 +1,223 @@
/*
* Copyright (C) 2013 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 android.os.Environment;
import android.security.Credentials;
import android.security.KeyStore;
import android.util.Log;
import com.android.internal.net.VpnProfile;
import com.android.org.bouncycastle.asn1.ASN1InputStream;
import com.android.org.bouncycastle.asn1.ASN1Sequence;
import com.android.org.bouncycastle.asn1.DEROctetString;
import com.android.org.bouncycastle.asn1.x509.BasicConstraints;
import junit.framework.Assert;
import libcore.io.Streams;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.PrivateKey;
import java.security.UnrecoverableEntryException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
/**
* Certificate installer helper to extract information from a provided file
* and install certificates to keystore.
*/
public class CertInstallerHelper {
private static final String TAG = "CertInstallerHelper";
/* Define a password to unlock keystore after it is reset */
private static final String CERT_STORE_PASSWORD = "password";
private final int mUid = KeyStore.UID_SELF;
private PrivateKey mUserKey; // private key
private X509Certificate mUserCert; // user certificate
private List<X509Certificate> mCaCerts = new ArrayList<X509Certificate>();
private KeyStore mKeyStore = KeyStore.getInstance();
/**
* Unlock keystore and set password
*/
public CertInstallerHelper() {
mKeyStore.reset();
mKeyStore.onUserPasswordChanged(CERT_STORE_PASSWORD);
}
private void extractCertificate(String certFile, String password) {
InputStream in = null;
final byte[] raw;
java.security.KeyStore keystore = null;
try {
// Read .p12 file from SDCARD and extract with password
in = new FileInputStream(new File(
Environment.getExternalStorageDirectory(), certFile));
raw = Streams.readFully(in);
keystore = java.security.KeyStore.getInstance("PKCS12");
PasswordProtection passwordProtection = new PasswordProtection(password.toCharArray());
keystore.load(new ByteArrayInputStream(raw), passwordProtection.getPassword());
// Install certificates and private keys
Enumeration<String> aliases = keystore.aliases();
if (!aliases.hasMoreElements()) {
Assert.fail("key store failed to put in keychain");
}
ArrayList<String> aliasesList = Collections.list(aliases);
// The keystore is initialized for each test case, there will
// be only one alias in the keystore
Assert.assertEquals(1, aliasesList.size());
String alias = aliasesList.get(0);
java.security.KeyStore.Entry entry = keystore.getEntry(alias, passwordProtection);
Log.d(TAG, "extracted alias = " + alias + ", entry=" + entry.getClass());
if (entry instanceof PrivateKeyEntry) {
Assert.assertTrue(installFrom((PrivateKeyEntry) entry));
}
} catch (IOException e) {
Assert.fail("Failed to read certficate: " + e);
} catch (KeyStoreException e) {
Log.e(TAG, "failed to extract certificate" + e);
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "failed to extract certificate" + e);
} catch (CertificateException e) {
Log.e(TAG, "failed to extract certificate" + e);
} catch (UnrecoverableEntryException e) {
Log.e(TAG, "failed to extract certificate" + e);
}
finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
Log.e(TAG, "close FileInputStream error: " + e);
}
}
}
}
/**
* Extract private keys, user certificates and ca certificates
*/
private synchronized boolean installFrom(PrivateKeyEntry entry) {
mUserKey = entry.getPrivateKey();
mUserCert = (X509Certificate) entry.getCertificate();
Certificate[] certs = entry.getCertificateChain();
Log.d(TAG, "# certs extracted = " + certs.length);
mCaCerts = new ArrayList<X509Certificate>(certs.length);
for (Certificate c : certs) {
X509Certificate cert = (X509Certificate) c;
if (isCa(cert)) {
mCaCerts.add(cert);
}
}
Log.d(TAG, "# ca certs extracted = " + mCaCerts.size());
return true;
}
private boolean isCa(X509Certificate cert) {
try {
byte[] asn1EncodedBytes = cert.getExtensionValue("2.5.29.19");
if (asn1EncodedBytes == null) {
return false;
}
DEROctetString derOctetString = (DEROctetString)
new ASN1InputStream(asn1EncodedBytes).readObject();
byte[] octets = derOctetString.getOctets();
ASN1Sequence sequence = (ASN1Sequence)
new ASN1InputStream(octets).readObject();
return BasicConstraints.getInstance(sequence).isCA();
} catch (IOException e) {
return false;
}
}
/**
* Extract certificate from the given file, and install it to keystore
* @param name certificate name
* @param certFile .p12 file which includes certificates
* @param password password to extract the .p12 file
*/
public void installCertificate(VpnProfile profile, String certFile, String password) {
// extract private keys, certificates from the provided file
extractCertificate(certFile, password);
// install certificate to the keystore
int flags = KeyStore.FLAG_ENCRYPTED;
try {
if (mUserKey != null) {
Log.v(TAG, "has private key");
String key = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert;
byte[] value = mUserKey.getEncoded();
if (!mKeyStore.importKey(key, value, mUid, flags)) {
Log.e(TAG, "Failed to install " + key + " as user " + mUid);
return;
}
Log.v(TAG, "install " + key + " as user " + mUid + " is successful");
}
if (mUserCert != null) {
String certName = Credentials.USER_CERTIFICATE + profile.ipsecUserCert;
byte[] certData = Credentials.convertToPem(mUserCert);
if (!mKeyStore.put(certName, certData, mUid, flags)) {
Log.e(TAG, "Failed to install " + certName + " as user " + mUid);
return;
}
Log.v(TAG, "install " + certName + " as user" + mUid + " is successful.");
}
if (!mCaCerts.isEmpty()) {
String caListName = Credentials.CA_CERTIFICATE + profile.ipsecCaCert;
X509Certificate[] caCerts = mCaCerts.toArray(new X509Certificate[mCaCerts.size()]);
byte[] caListData = Credentials.convertToPem(caCerts);
if (!mKeyStore.put(caListName, caListData, mUid, flags)) {
Log.e(TAG, "Failed to install " + caListName + " as user " + mUid);
return;
}
Log.v(TAG, " install " + caListName + " as user " + mUid + " is successful");
}
} catch (CertificateEncodingException e) {
Log.e(TAG, "Exception while convert certificates to pem " + e);
throw new AssertionError(e);
} catch (IOException e) {
Log.e(TAG, "IOException while convert to pem: " + e);
}
}
public int getUid() {
return mUid;
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2013 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 com.android.internal.net.VpnProfile;
/**
* Wrapper for VPN Profile and associated certificate files
*/
public class VpnInfo {
// VPN Profile
private VpnProfile mVpnProfile;
// Certificate file in PC12 format for user certificates and private keys
private String mCertificateFile = null;
// Password to extract certificates from the file
private String mPassword = null;
public VpnInfo(VpnProfile vpnProfile, String certFile, String password) {
mVpnProfile = vpnProfile;
mCertificateFile = certFile;
mPassword = password;
}
public VpnInfo(VpnProfile vpnProfile) {
mVpnProfile = vpnProfile;
}
public void setVpnProfile(VpnProfile vpnProfile) {
mVpnProfile = vpnProfile;
}
public void setCertificateFile(String certFile) {
mCertificateFile = certFile;
}
public void setPassword(String password) {
mPassword = password;
}
public VpnProfile getVpnProfile() {
return mVpnProfile;
}
public String getCertificateFile() {
return mCertificateFile;
}
public String getPassword() {
return mPassword;
}
}

View File

@@ -0,0 +1,246 @@
/*
* Copyright (C) 2013 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 android.util.Log;
import com.android.internal.net.VpnProfile;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
/**
* Parse VPN profiles from an XML file
*/
public class VpnProfileParser {
private final static String TAG = "VpnProfileParser";
private static Map<Integer, VpnInfo> mVpnPool = new HashMap<Integer, VpnInfo>();
static DefaultHandler mHandler = new DefaultHandler() {
boolean name;
boolean type;
boolean server;
boolean username;
boolean password;
boolean dnsServers;
boolean searchDomains;
boolean routes;
boolean mppe;
boolean l2tpSecret;
boolean ipsecIdentifier;
boolean ipsecSecret;
boolean ipsecUserCert;
boolean ipsecCaCert;
boolean ipsecServerCert;
boolean certFile;
boolean certFilePassword;
VpnProfile profile = null;
VpnInfo vpnInfo = null;
@Override
public void startElement(String uri, String localName, String tagName,
Attributes attributes) throws SAXException {
if (tagName.equalsIgnoreCase("vpn")) {
//create a new VPN profile
profile = new VpnProfile(Long.toHexString(System.currentTimeMillis()));
vpnInfo = new VpnInfo(profile);
}
if (tagName.equalsIgnoreCase("name")) {
name = true;
}
if (tagName.equalsIgnoreCase("type")) {
type = true;
}
if (tagName.equalsIgnoreCase("server")) {
server = true;
}
if (tagName.equalsIgnoreCase("username")) {
username = true;
}
if (tagName.equalsIgnoreCase("password")) {
password = true;
}
if (tagName.equalsIgnoreCase("dnsServers")) {
dnsServers = true;
}
if (tagName.equalsIgnoreCase("searchDomains")) {
searchDomains = true;
}
if (tagName.equalsIgnoreCase("mppe")) {
mppe = true;
}
if (tagName.equalsIgnoreCase("l2tpSecret")) {
l2tpSecret = true;
}
if (tagName.equalsIgnoreCase("ipsecIdentifier")) {
ipsecIdentifier = true;
}
if (tagName.equalsIgnoreCase("ipsecSecret")) {
ipsecSecret = true;
}
if (tagName.equalsIgnoreCase("ipsecUserCert")) {
ipsecUserCert = true;
}
if (tagName.equalsIgnoreCase("ipsecCaCert")) {
ipsecCaCert = true;
}
if (tagName.equalsIgnoreCase("ipsecServerCert")) {
ipsecServerCert = true;
}
if (tagName.equalsIgnoreCase("routes")) {
routes = true;
}
if (tagName.equalsIgnoreCase("cert-file")) {
certFile = true;
}
if (tagName.equalsIgnoreCase("cert-file-password")) {
certFilePassword = true;
}
}
@Override
public void endElement(String uri, String localName, String tagName) throws SAXException {
if (tagName.equalsIgnoreCase("vpn")) {
mVpnPool.put(profile.type, vpnInfo);
}
}
@Override
public void characters(char ch[], int start, int length) throws SAXException {
String strValue = new String(ch, start, length);
if (name) {
profile.name = strValue;
name = false;
}
if (type) {
int t = getVpnProfileType(strValue);
if (t < 0) {
throw new SAXException("not a valid VPN type");
} else {
profile.type = t;
}
type = false;
}
if (server) {
profile.server = strValue;
server = false;
}
if (username) {
profile.username = strValue;
username = false;
}
if (password) {
profile.password = strValue;
password = false;
}
if (dnsServers) {
profile.dnsServers = strValue;
dnsServers = false;
}
if (searchDomains) {
profile.searchDomains = strValue;
searchDomains = false;
}
if (mppe) {
profile.mppe = Boolean.valueOf(strValue);
mppe = false;
}
if (l2tpSecret) {
profile.l2tpSecret = strValue;
l2tpSecret = false;
}
if (ipsecIdentifier) {
profile.ipsecIdentifier = strValue;
ipsecIdentifier = false;
}
if (ipsecSecret) {
profile.ipsecSecret = strValue;
ipsecSecret = false;
}
if (ipsecUserCert) {
profile.ipsecUserCert = strValue;
ipsecUserCert = false;
}
if (ipsecCaCert) {
profile.ipsecCaCert = strValue;
ipsecCaCert = false;
}
if (ipsecServerCert) {
profile.ipsecServerCert = strValue;
ipsecServerCert = false;
}
if (routes) {
profile.routes = strValue;
routes = false;
}
if (certFile) {
vpnInfo.setCertificateFile(strValue);
certFile = false;
}
if (certFilePassword) {
vpnInfo.setPassword(strValue);
certFilePassword = false;
}
}
private int getVpnProfileType(String type) {
if (type.equalsIgnoreCase("TYPE_PPTP")) {
return VpnProfile.TYPE_PPTP;
} else if (type.equalsIgnoreCase("TYPE_L2TP_IPSEC_PSK")) {
return VpnProfile.TYPE_L2TP_IPSEC_PSK;
} else if (type.equalsIgnoreCase("TYPE_L2TP_IPSEC_RSA")) {
return VpnProfile.TYPE_L2TP_IPSEC_RSA;
} else if (type.equalsIgnoreCase("TYPE_IPSEC_XAUTH_PSK")) {
return VpnProfile.TYPE_IPSEC_XAUTH_PSK;
} else if (type.equalsIgnoreCase("TYPE_IPSEC_XAUTH_RSA")) {
return VpnProfile.TYPE_IPSEC_XAUTH_RSA;
} else if (type.equalsIgnoreCase("TYPE_IPSEC_HYBRID_RSA")) {
return VpnProfile.TYPE_IPSEC_HYBRID_RSA;
} else {
Log.v(TAG, "Invalid VPN type: " + type);
return -1;
}
}
};
public static Map<Integer, VpnInfo> parse(InputStream in) {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
saxParser.parse(in, mHandler);
} catch (SAXException e) {
Log.e(TAG, "Parse vpn profile exception: " + e.toString());
} catch (IOException e) {
Log.e(TAG, "Parse vpn profile exception: " + e.toString());
} catch (ParserConfigurationException e) {
Log.e(TAG, "Parse vpn profile exception: " + e.toString());
} finally {
return mVpnPool;
}
}
}

View File

@@ -0,0 +1,446 @@
/*
* Copyright (C) 2013 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 android.content.Context;
import android.net.IConnectivityManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.security.Credentials;
import android.security.KeyStore;
import android.security.NetworkSecurityPolicy;
import android.test.InstrumentationTestCase;
import android.test.InstrumentationTestRunner;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
import java.net.HttpURLConnection;
import java.net.URL;
import junit.framework.Assert;
import libcore.io.Streams;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
/**
* Legacy VPN connection tests
*
* To run the test, use command:
* adb shell am instrument -e class com.android.settings.vpn2.VpnTests -e profile foo.xml
* -w com.android.settings.tests/android.test.InstrumentationTestRunner
*
* VPN profiles are saved in an xml file and will be loaded through {@link VpnProfileParser}.
* Push the profile (foo.xml) to the external storage, e.g adb push foo.xml /sdcard/ before running
* the above command.
*
* A typical profile looks like the following:
* <vpn>
* <name></name>
* <type></type>
* <server></server>
* <username></username>
* <password></password>
* <dnsServers></dnsServers>
* <searchDomains></searchDomains>
* <routes></routes>
* <l2tpSecret></l2tpSecret>
* <ipsecIdentifier></ipsecIdentifier>
* <ipsecSecret></ipsecSecret>
* <ipsecUserCert></ipsecUserCert>
* <ipsecCaCert></ipsecCaCert>
* <ipsecServerCert></ipsecServerCert>
* </vpn>
* VPN types include: TYPE_PPTP, TYPE_L2TP_IPSEC_PSK, TYPE_L2TP_IPSEC_RSA,
* TYPE_IPSEC_XAUTH_PSK, TYPE_IPSEC_XAUTH_RSA, TYPE_IPSEC_HYBRID_RSA
*/
public class VpnTests extends InstrumentationTestCase {
private static final String TAG = "VpnTests";
/* Maximum time to wait for VPN connection */
private static final long MAX_CONNECTION_TIME = 5 * 60 * 1000;
private static final long VPN_STAY_TIME = 60 * 1000;
private static final int MAX_DISCONNECTION_TRIES = 3;
private static final String EXTERNAL_SERVER =
"http://ip2country.sourceforge.net/ip2c.php?format=JSON";
private static final String VPN_INTERFACE = "ppp0";
private final IConnectivityManager mService = IConnectivityManager.Stub
.asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
private Map<Integer, VpnInfo> mVpnInfoPool = null;
private Context mContext;
private CertInstallerHelper mCertHelper = null;
private KeyStore mKeyStore = KeyStore.getInstance();
private String mPreviousIpAddress = null;
private boolean DEBUG = false;
@Override
protected void setUp() throws Exception {
super.setUp();
InputStream in = null;
InstrumentationTestRunner mRunner = (InstrumentationTestRunner)getInstrumentation();
mContext = mRunner.getContext();
Bundle arguments = mRunner.getArguments();
String PROFILE_NAME = arguments.getString("profile");
Assert.assertNotNull("Push profile to external storage and load with"
+ "'-e profile <filename>'", PROFILE_NAME);
File profileFile = new File(Environment.getExternalStorageDirectory(), PROFILE_NAME);
in = new FileInputStream(profileFile);
mVpnInfoPool = VpnProfileParser.parse(in);
Assert.assertNotNull("no VPN profiles are parsed", mVpnInfoPool);
if (DEBUG) {
Log.v(TAG, "print out the vpn profiles");
for (Map.Entry<Integer, VpnInfo> profileEntrySet: mVpnInfoPool.entrySet()) {
VpnInfo vpnInfo = profileEntrySet.getValue();
printVpnProfile(vpnInfo.getVpnProfile());
if (vpnInfo.getCertificateFile() != null) {
Log.d(TAG, "certificate file for this vpn is " + vpnInfo.getCertificateFile());
}
if (vpnInfo.getPassword() != null) {
Log.d(TAG, "password for the certificate file is: " + vpnInfo.getPassword());
}
}
}
// disconnect existing vpn if there is any
LegacyVpnInfo oldVpn = mService.getLegacyVpnInfo(UserHandle.myUserId());
if (oldVpn != null) {
Log.v(TAG, "disconnect legacy VPN");
disconnect();
// wait till the legacy VPN is disconnected.
int tries = 0;
while (tries < MAX_DISCONNECTION_TRIES &&
mService.getLegacyVpnInfo(UserHandle.myUserId()) != null) {
tries++;
Thread.sleep(10 * 1000);
Log.v(TAG, "Wait for legacy VPN to be disconnected.");
}
Assert.assertNull("Failed to disconect VPN",
mService.getLegacyVpnInfo(UserHandle.myUserId()));
// wait for 30 seconds after the previous VPN is disconnected.
sleep(30 * 1000);
}
// Create CertInstallerHelper to initialize the keystore
mCertHelper = new CertInstallerHelper();
}
@Override
protected void tearDown() throws Exception {
sleep(VPN_STAY_TIME);
super.tearDown();
}
private void printVpnProfile(VpnProfile profile) {
Log.v(TAG, "profile: ");
Log.v(TAG, "key: " + profile.key);
Log.v(TAG, "name: " + profile.name);
Log.v(TAG, "type: " + profile.type);
Log.v(TAG, "server: " + profile.server);
Log.v(TAG, "username: " + profile.username);
Log.v(TAG, "password: " + profile.password);
Log.v(TAG, "dnsServers: " + profile.dnsServers);
Log.v(TAG, "searchDomains: " + profile.searchDomains);
Log.v(TAG, "routes: " + profile.routes);
Log.v(TAG, "mppe: " + profile.mppe);
Log.v(TAG, "l2tpSecret: " + profile.l2tpSecret);
Log.v(TAG, "ipsecIdentifier: " + profile.ipsecIdentifier);
Log.v(TAG, "ipsecSecret: " + profile.ipsecSecret);
Log.v(TAG, "ipsecUserCert: " + profile.ipsecUserCert);
Log.v(TAG, "ipsecCaCert: " + profile.ipsecCaCert);
Log.v(TAG, "ipsecServerCert: " + profile.ipsecServerCert);
}
private void printKeyStore(VpnProfile profile) {
// print out the information from keystore
String privateKey = "";
String userCert = "";
String caCert = "";
String serverCert = "";
if (!profile.ipsecUserCert.isEmpty()) {
privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert;
byte[] value = mKeyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert);
userCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
}
if (!profile.ipsecCaCert.isEmpty()) {
byte[] value = mKeyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert);
caCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
}
if (!profile.ipsecServerCert.isEmpty()) {
byte[] value = mKeyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert);
serverCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
}
Log.v(TAG, "privateKey: \n" + ((privateKey == null) ? "" : privateKey));
Log.v(TAG, "userCert: \n" + ((userCert == null) ? "" : userCert));
Log.v(TAG, "caCert: \n" + ((caCert == null) ? "" : caCert));
Log.v(TAG, "serverCert: \n" + ((serverCert == null) ? "" : serverCert));
}
/**
* Connect legacy VPN
*/
private void connect(VpnProfile profile) throws Exception {
try {
mService.startLegacyVpn(profile);
} catch (IllegalStateException e) {
fail(String.format("start legacy vpn: %s failed: %s", profile.name, e.toString()));
}
}
/**
* Disconnect legacy VPN
*/
private void disconnect() throws Exception {
try {
mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, UserHandle.myUserId());
} catch (RemoteException e) {
Log.e(TAG, String.format("disconnect VPN exception: %s", e.toString()));
}
}
/**
* Get external IP address
*/
private String getIpAddress() {
String ip = null;
HttpURLConnection urlConnection = null;
// TODO: Rewrite this test to use an HTTPS URL.
// Because this test uses cleartext HTTP, the network security policy of this app needs to
// be temporarily relaxed to permit such traffic.
NetworkSecurityPolicy networkSecurityPolicy = NetworkSecurityPolicy.getInstance();
boolean cleartextTrafficPermittedBeforeTest =
networkSecurityPolicy.isCleartextTrafficPermitted();
networkSecurityPolicy.setCleartextTrafficPermitted(true);
try {
URL url = new URL(EXTERNAL_SERVER);
urlConnection = (HttpURLConnection) url.openConnection();
Log.i(TAG, "Response from httpget: " + urlConnection.getResponseCode());
InputStream is = urlConnection.getInputStream();
String response;
try {
response = new String(Streams.readFully(is), StandardCharsets.UTF_8);
} finally {
is.close();
}
JSONObject json_data = new JSONObject(response);
ip = json_data.getString("ip");
Log.v(TAG, "json_data: " + ip);
} catch (IllegalArgumentException e) {
Log.e(TAG, "exception while getting external IP: " + e.toString());
} catch (IOException e) {
Log.e(TAG, "IOException while getting IP: " + e.toString());
} catch (JSONException e) {
Log.e(TAG, "exception while creating JSONObject: " + e.toString());
} finally {
networkSecurityPolicy.setCleartextTrafficPermitted(cleartextTrafficPermittedBeforeTest);
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return ip;
}
/**
* Verify the vpn connection by checking the VPN state and external IP
*/
private void validateVpnConnection(VpnProfile profile) throws Exception {
validateVpnConnection(profile, false);
}
/**
* Verify the vpn connection by checking the VPN state, external IP or ping test
*/
private void validateVpnConnection(VpnProfile profile, boolean pingTestFlag) throws Exception {
LegacyVpnInfo legacyVpnInfo = mService.getLegacyVpnInfo(UserHandle.myUserId());
Assert.assertTrue(legacyVpnInfo != null);
long start = System.currentTimeMillis();
while (((System.currentTimeMillis() - start) < MAX_CONNECTION_TIME) &&
(legacyVpnInfo.state != LegacyVpnInfo.STATE_CONNECTED)) {
Log.v(TAG, "vpn state: " + legacyVpnInfo.state);
sleep(10 * 1000);
legacyVpnInfo = mService.getLegacyVpnInfo(UserHandle.myUserId());
}
// the vpn state should be CONNECTED
Assert.assertTrue(legacyVpnInfo.state == LegacyVpnInfo.STATE_CONNECTED);
if (pingTestFlag) {
Assert.assertTrue(pingTest(profile.server));
} else {
String curIpAddress = getIpAddress();
// the outgoing IP address should be the same as the VPN server address
Assert.assertEquals(profile.server, curIpAddress);
}
}
private boolean pingTest(String server) {
final long PING_TIMER = 3 * 60 * 1000; // 3 minutes
if (server == null || server.isEmpty()) {
return false;
}
long startTime = System.currentTimeMillis();
while ((System.currentTimeMillis() - startTime) < PING_TIMER) {
try {
Log.v(TAG, "Start ping test, ping " + server);
Process p = Runtime.getRuntime().exec("ping -c 10 -w 100 " + server);
int status = p.waitFor();
if (status == 0) {
// if any of the ping test is successful, return true
return true;
}
} catch (UnknownHostException e) {
Log.e(TAG, "Ping test Fail: Unknown Host");
} catch (IOException e) {
Log.e(TAG, "Ping test Fail: IOException");
} catch (InterruptedException e) {
Log.e(TAG, "Ping test Fail: InterruptedException");
}
}
// ping test timeout
return false;
}
/**
* Install certificates from a file loaded in external stroage on the device
* @param profile vpn profile
* @param fileName certificate file name
* @param password password to extract certificate file
*/
private void installCertificatesFromFile(VpnProfile profile, String fileName, String password)
throws Exception {
if (profile == null || fileName == null || password == null) {
throw new Exception ("vpn profile, certificate file name and password can not be null");
}
int curUid = mContext.getUserId();
mCertHelper.installCertificate(profile, fileName, password);
if (DEBUG) {
printKeyStore(profile);
}
}
private void sleep(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
Log.e(TAG, "interrupted: " + e.toString());
}
}
/**
* Test PPTP VPN connection
*/
@LargeTest
public void testPPTPConnection() throws Exception {
mPreviousIpAddress = getIpAddress();
VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_PPTP);
VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
connect(vpnProfile);
validateVpnConnection(vpnProfile);
}
/**
* Test L2TP/IPSec PSK VPN connection
*/
@LargeTest
public void testL2tpIpsecPskConnection() throws Exception {
mPreviousIpAddress = getIpAddress();
VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_L2TP_IPSEC_PSK);
VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
connect(vpnProfile);
validateVpnConnection(vpnProfile);
}
/**
* Test L2TP/IPSec RSA VPN connection
*/
@LargeTest
public void testL2tpIpsecRsaConnection() throws Exception {
mPreviousIpAddress = getIpAddress();
VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_L2TP_IPSEC_RSA);
VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
if (DEBUG) {
printVpnProfile(vpnProfile);
}
String certFile = curVpnInfo.getCertificateFile();
String password = curVpnInfo.getPassword();
installCertificatesFromFile(vpnProfile, certFile, password);
connect(vpnProfile);
validateVpnConnection(vpnProfile);
}
/**
* Test IPSec Xauth RSA VPN connection
*/
@LargeTest
public void testIpsecXauthRsaConnection() throws Exception {
mPreviousIpAddress = getIpAddress();
VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_IPSEC_XAUTH_RSA);
VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
if (DEBUG) {
printVpnProfile(vpnProfile);
}
String certFile = curVpnInfo.getCertificateFile();
String password = curVpnInfo.getPassword();
installCertificatesFromFile(vpnProfile, certFile, password);
connect(vpnProfile);
validateVpnConnection(vpnProfile);
}
/**
* Test IPSec Xauth PSK VPN connection
*/
@LargeTest
public void testIpsecXauthPskConnection() throws Exception {
VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_IPSEC_XAUTH_PSK);
VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
if (DEBUG) {
printVpnProfile(vpnProfile);
}
connect(vpnProfile);
validateVpnConnection(vpnProfile, true);
}
/**
* Test IPSec Hybrid RSA VPN connection
*/
@LargeTest
public void testIpsecHybridRsaConnection() throws Exception {
mPreviousIpAddress = getIpAddress();
VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_IPSEC_HYBRID_RSA);
VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
if (DEBUG) {
printVpnProfile(vpnProfile);
}
connect(vpnProfile);
validateVpnConnection(vpnProfile);
}
}