diff options
Diffstat (limited to 'tests')
10 files changed, 1277 insertions, 65 deletions
diff --git a/tests/AndroidTests/src/com/android/unit_tests/AppCacheTest.java b/tests/AndroidTests/src/com/android/unit_tests/AppCacheTest.java index f9af436..3daa8ab 100755 --- a/tests/AndroidTests/src/com/android/unit_tests/AppCacheTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/AppCacheTest.java @@ -121,6 +121,7 @@ public class AppCacheTest extends AndroidTestCase { } @LargeTest public void testFreeApplicationCacheAllFiles() throws Exception { + boolean TRACKING = true; StatFs st = new StatFs("/data"); long blks1 = getFreeStorageBlks(st); long availableMem = getFreeStorageSize(st); @@ -128,11 +129,11 @@ public class AppCacheTest extends AndroidTestCase { assertNotNull(cacheDir); createTestFiles1(cacheDir, "testtmpdir", 5); long blks2 = getFreeStorageBlks(st); - if(localLOGV) Log.i(TAG, "blk1="+blks1+", blks2="+blks2); + if(localLOGV || TRACKING) Log.i(TAG, "blk1="+blks1+", blks2="+blks2); //this should free up the test files that were created earlier invokePMFreeApplicationCache(availableMem); long blks3 = getFreeStorageBlks(st); - if(localLOGV) Log.i(TAG, "blks3="+blks3); + if(localLOGV || TRACKING) Log.i(TAG, "blks3="+blks3); verifyTestFiles1(cacheDir, "testtmpdir", 5); } @@ -629,15 +630,16 @@ public class AppCacheTest extends AndroidTestCase { @SmallTest public void testFreeStorage() throws Exception { + boolean TRACKING = true; StatFs st = new StatFs("/data"); long blks1 = getFreeStorageBlks(st); - if(localLOGV) Log.i(TAG, "Available free blocks="+blks1); + if(localLOGV || TRACKING) Log.i(TAG, "Available free blocks="+blks1); long availableMem = getFreeStorageSize(st); File cacheDir = mContext.getCacheDir(); assertNotNull(cacheDir); createTestFiles1(cacheDir, "testtmpdir", 5); long blks2 = getFreeStorageBlks(st); - if(localLOGV) Log.i(TAG, "Available blocks after writing test files in application cache="+blks2); + if(localLOGV || TRACKING) Log.i(TAG, "Available blocks after writing test files in application cache="+blks2); // Create receiver and register it FreeStorageReceiver receiver = new FreeStorageReceiver(); mContext.registerReceiver(receiver, new IntentFilter(FreeStorageReceiver.ACTION_FREE)); @@ -646,7 +648,7 @@ public class AppCacheTest extends AndroidTestCase { // Invoke PackageManager api invokePMFreeStorage(availableMem, receiver, pi); long blks3 = getFreeStorageBlks(st); - if(localLOGV) Log.i(TAG, "Available blocks after freeing cache"+blks3); + if(localLOGV || TRACKING) Log.i(TAG, "Available blocks after freeing cache"+blks3); assertEquals(receiver.getResultCode(), 1); mContext.unregisterReceiver(receiver); // Verify result diff --git a/tests/AndroidTests/src/com/android/unit_tests/DbSSLSessionCacheTest.java b/tests/AndroidTests/src/com/android/unit_tests/DbSSLSessionCacheTest.java new file mode 100644 index 0000000..77460be --- /dev/null +++ b/tests/AndroidTests/src/com/android/unit_tests/DbSSLSessionCacheTest.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2008 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.unit_tests; + +import android.content.ContentResolver; +import android.database.Cursor; +import android.database.sqlite.SQLiteOpenHelper; +import android.provider.Settings; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.Suppress; + +import com.google.android.net.GoogleHttpClient; + +import com.android.internal.net.DbSSLSessionCache; +import com.android.internal.net.SSLSessionCache; +import com.android.internal.net.DbSSLSessionCache.DatabaseHelper; + +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; + +import java.io.IOException; +import java.security.Principal; +import java.security.cert.Certificate; + +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionContext; +import javax.security.cert.X509Certificate; + +/** Unit test for SSL session caching with {@link GoogleHttpClient}. + * Uses network resources. + */ +@Suppress +public class DbSSLSessionCacheTest extends AndroidTestCase { + + protected void setUp() throws Exception { + } + + protected void tearDown() throws Exception { + } + + @LargeTest + public void testSslCacheSettings() throws Exception { + ContentResolver resolver = getContext().getContentResolver(); + Settings.Gservices.putString(resolver, Settings.Gservices.SSL_SESSION_CACHE, + "0"); + assertFalse(SSLSessionCache.isEnabled(getContext().getContentResolver())); + + resolver = getContext().getContentResolver(); + Settings.Gservices.putString(resolver, Settings.Gservices.SSL_SESSION_CACHE, + "db"); + assertTrue(SSLSessionCache.isEnabled(getContext().getContentResolver())); + } + + /** + * We want to test the actual database write - the actual hooking into + * low-level SSL is tested. + */ + @LargeTest + public void testSslCacheAdd() throws Exception { + // Let's verify the database has the rows. + // Use internal details of the implementation - could make the field + // visible for testing, but it's same. + + // Use default database + DbSSLSessionCache cache = new DbSSLSessionCache(getContext()); + cache.clear(); + + + makeRequestInNewContext("https://www.google.com"); + + // Verify the key was inserted + SQLiteOpenHelper helper = new DatabaseHelper(getContext()); + Cursor query = null; + try { + query = helper.getReadableDatabase().query(DbSSLSessionCache.SSL_CACHE_TABLE, + new String[] {"hostport"}, null, + null, null, null, null); + + assertTrue(query.moveToFirst()); // one row inserted + String hostPort = query.getString(0); + assertEquals(hostPort, "www.google.com:443"); + } finally { + query.close(); + } + } + + @LargeTest + public void testExpire() throws Exception { + DatabaseHelper helper = new DatabaseHelper(getContext()); + // clean up + DbSSLSessionCache cache = new DbSSLSessionCache(helper); + cache.clear(); + + long t0 = System.currentTimeMillis(); + for (int i = 0; i < DbSSLSessionCache.MAX_CACHE_SIZE + 2; i++) { + final int port = i; + cache.putSessionData(new MockSession() { + + public String getPeerHost() { + return "test.host.com"; + } + + public int getPeerPort() { + return port; + } + }, new byte[256]); + } + long t1 = System.currentTimeMillis(); + + System.err.println("Time to insert " + + (DbSSLSessionCache.MAX_CACHE_SIZE + 2) + " " + (t1 - t0)); + + // first entry should have port 1. + Cursor query = helper.getReadableDatabase().query(DbSSLSessionCache.SSL_CACHE_TABLE, + new String[] {"hostport", "session"}, null, + null, null, null, null); + + int cnt = query.getCount(); + + assertTrue(query.moveToFirst()); // one row inserted + String hostPort = query.getString(0); + assertEquals("test.host.com:2", hostPort); + while (query.moveToNext()) { + hostPort = query.getString(0); + String session = query.getString(1); + } + long t2 = System.currentTimeMillis(); + System.err.println("Time to load " + cnt + " " + (t2 - t1)); + + query.close(); + } + + private void makeRequestInNewContext(String url) throws IOException { + GoogleHttpClient client = new GoogleHttpClient(getContext(), "Test", + false /* no gzip */); + + try { + // Note: we must test against a real server, because the connection + // gets established before the interceptor can crash the request. + HttpGet method = new HttpGet(url); + HttpResponse response = client.execute(method); + } finally { + client.close(); + } + } + + private static class MockSession implements SSLSession { + + public String getPeerHost() { + throw new UnsupportedOperationException(); + } + + + public int getPeerPort() { + throw new UnsupportedOperationException(); + } + + + + public int getApplicationBufferSize() { + throw new UnsupportedOperationException(); + } + + + public String getCipherSuite() { + throw new UnsupportedOperationException(); + } + + + public long getCreationTime() { + throw new UnsupportedOperationException(); + } + + + public byte[] getId() { + throw new UnsupportedOperationException(); + } + + + public long getLastAccessedTime() { + throw new UnsupportedOperationException(); + } + + + public Certificate[] getLocalCertificates() { + throw new UnsupportedOperationException(); + } + + + public Principal getLocalPrincipal() { + throw new UnsupportedOperationException(); + } + + + public int getPacketBufferSize() { + throw new UnsupportedOperationException(); + } + + + public X509Certificate[] getPeerCertificateChain() + throws SSLPeerUnverifiedException { + throw new UnsupportedOperationException(); + } + + + public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { + throw new UnsupportedOperationException(); + } + + + public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + throw new UnsupportedOperationException(); + } + + + public String getProtocol() { + throw new UnsupportedOperationException(); + } + + + public SSLSessionContext getSessionContext() { + throw new UnsupportedOperationException(); + } + + + public Object getValue(String name) { + throw new UnsupportedOperationException(); + } + + + public String[] getValueNames() { + throw new UnsupportedOperationException(); + } + + + public void invalidate() { + throw new UnsupportedOperationException(); + } + + + public boolean isValid() { + throw new UnsupportedOperationException(); + } + + + public void putValue(String name, Object value) { + throw new UnsupportedOperationException(); + } + + + public void removeValue(String name) { + throw new UnsupportedOperationException(); + } + } + + +} diff --git a/tests/CoreTests/android/core/DatabaseSessionCache.java b/tests/CoreTests/android/core/DatabaseSessionCache.java new file mode 100644 index 0000000..c344d9c --- /dev/null +++ b/tests/CoreTests/android/core/DatabaseSessionCache.java @@ -0,0 +1,312 @@ +// Copyright 2009 The Android Open Source Project + +package android.core; + +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; +import android.content.ContentValues; +import android.content.Context; + +import org.apache.commons.codec.binary.Base64; +import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache; + +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.net.ssl.SSLSession; + +/** + * Hook into harmony SSL cache to persist the SSL sessions. + * + * Current implementation is suitable for saving a small number of hosts - + * like google services. It can be extended with expiration and more features + * to support more hosts. + * + * {@hide} + */ +public class DatabaseSessionCache implements SSLClientSessionCache { + private static final String TAG = "SslSessionCache"; + static DatabaseHelper sDefaultDatabaseHelper; + + private DatabaseHelper mDatabaseHelper; + + /** + * Table where sessions are stored. + */ + public static final String SSL_CACHE_TABLE = "ssl_sessions"; + + private static final String SSL_CACHE_ID = "_id"; + + /** + * Key is host:port - port is not optional. + */ + private static final String SSL_CACHE_HOSTPORT = "hostport"; + + /** + * Base64-encoded DER value of the session. + */ + private static final String SSL_CACHE_SESSION = "session"; + + /** + * Time when the record was added - should be close to the time + * of the initial session negotiation. + */ + private static final String SSL_CACHE_TIME_SEC = "time_sec"; + + public static final String DATABASE_NAME = "ssl_sessions.db"; + + public static final int DATABASE_VERSION = 1; + + /** public for testing + */ + public static final int SSL_CACHE_ID_COL = 0; + public static final int SSL_CACHE_HOSTPORT_COL = 1; + public static final int SSL_CACHE_SESSION_COL = 2; + public static final int SSL_CACHE_TIME_SEC_COL = 3; + + private static final String SAVE_ON_ADD = "save_on_add"; + + static boolean sHookInitializationDone = false; + + public static final int MAX_CACHE_SIZE = 256; + + private static final Map<String, byte[]> mExternalCache = + new LinkedHashMap<String, byte[]>(MAX_CACHE_SIZE, 0.75f, true) { + @Override + public boolean removeEldestEntry( + Map.Entry<String, byte[]> eldest) { + boolean shouldDelete = this.size() > MAX_CACHE_SIZE; + + // TODO: delete from DB + return shouldDelete; + } + }; + static boolean mNeedsCacheLoad = true; + + public static final String[] PROJECTION = new String[] { + SSL_CACHE_ID, + SSL_CACHE_HOSTPORT, + SSL_CACHE_SESSION, + SSL_CACHE_TIME_SEC + }; + + /** + * This class needs to be installed as a hook, if the security property + * is set. Getting the right classloader may be fun since we don't use + * Provider to get its classloader, but in android this is in same + * loader with AndroidHttpClient. + * + * This constructor will use the default database. You must + * call init() before to specify the context used for the database and + * check settings. + */ + public DatabaseSessionCache() { + Log.v(TAG, "Instance created."); + // May be null if caching is disabled - no sessions will be persisted. + this.mDatabaseHelper = sDefaultDatabaseHelper; + } + + /** + * Create a SslSessionCache instance, using the specified context to + * initialize the database. + * + * This constructor will use the default database - created the first + * time. + * + * @param activityContext + */ + public DatabaseSessionCache(Context activityContext) { + // Static init - only one initialization will happen. + // Each SslSessionCache is using the same DB. + init(activityContext); + // May be null if caching is disabled - no sessions will be persisted. + this.mDatabaseHelper = sDefaultDatabaseHelper; + } + + /** + * Create a SslSessionCache that uses a specific database. + * + * @param database + */ + public DatabaseSessionCache(DatabaseHelper database) { + this.mDatabaseHelper = database; + } + +// public static boolean enabled(Context androidContext) { +// String sslCache = Settings.Gservices.getString(androidContext.getContentResolver(), +// Settings.Gservices.SSL_SESSION_CACHE); +// +// if (Log.isLoggable(TAG, Log.DEBUG)) { +// Log.d(TAG, "enabled " + sslCache + " " + androidContext.getPackageName()); +// } +// +// return SAVE_ON_ADD.equals(sslCache); +// } + + /** + * You must call this method to enable SSL session caching for an app. + */ + public synchronized static void init(Context activityContext) { + // It is possible that multiple provider will try to install this hook. + // We want a single db per VM. + if (sHookInitializationDone) { + return; + } + + +// // More values can be added in future to provide different +// // behaviours, like 'batch save'. +// if (enabled(activityContext)) { + Context appContext = activityContext.getApplicationContext(); + sDefaultDatabaseHelper = new DatabaseHelper(appContext); + + // Set default SSLSocketFactory + // The property is defined in the javadocs for javax.net.SSLSocketFactory + // (no constant defined there) + // This should cover all code using SSLSocketFactory.getDefault(), + // including native http client and apache httpclient. + // MCS is using its own custom factory - will need special code. +// Security.setProperty("ssl.SocketFactory.provider", +// SslSocketFactoryWithCache.class.getName()); +// } + + // Won't try again. + sHookInitializationDone = true; + } + + public void putSessionData(SSLSession session, byte[] der) { + if (mDatabaseHelper == null) { + return; + } + if (mExternalCache.size() > MAX_CACHE_SIZE) { + // remove oldest. + Cursor byTime = mDatabaseHelper.getWritableDatabase().query(SSL_CACHE_TABLE, + PROJECTION, null, null, null, null, SSL_CACHE_TIME_SEC); + byTime.moveToFirst(); + // TODO: can I do byTime.deleteRow() ? + String hostPort = byTime.getString(SSL_CACHE_HOSTPORT_COL); + + mDatabaseHelper.getWritableDatabase().delete(SSL_CACHE_TABLE, + SSL_CACHE_HOSTPORT + "= ?" , new String[] { hostPort }); + } + // Serialize native session to standard DER encoding + long t0 = System.currentTimeMillis(); + + String b64 = new String(Base64.encodeBase64(der)); + String key = session.getPeerHost() + ":" + session.getPeerPort(); + + ContentValues values = new ContentValues(); + values.put(SSL_CACHE_HOSTPORT, key); + values.put(SSL_CACHE_SESSION, b64); + values.put(SSL_CACHE_TIME_SEC, System.currentTimeMillis() / 1000); + + synchronized (this.getClass()) { + mExternalCache.put(key, der); + + try { + mDatabaseHelper.getWritableDatabase().insert(SSL_CACHE_TABLE, null /*nullColumnHack */ , values); + } catch(SQLException ex) { + // Ignore - nothing we can do to recover, and caller shouldn't + // be affected. + Log.w(TAG, "Ignoring SQL exception when caching session", ex); + } + } + if (Log.isLoggable(TAG, Log.DEBUG)) { + long t1 = System.currentTimeMillis(); + Log.d(TAG, "New SSL session " + session.getPeerHost() + + " DER len: " + der.length + " " + (t1 - t0)); + } + + } + + public byte[] getSessionData(String host, int port) { + // Current (simple) implementation does a single lookup to DB, then saves + // all entries to the cache. + + // This works for google services - i.e. small number of certs. + // If we extend this to all processes - we should hold a separate cache + // or do lookups to DB each time. + if (mDatabaseHelper == null) { + return null; + } + synchronized(this.getClass()) { + if (mNeedsCacheLoad) { + // Don't try to load again, if something is wrong on the first + // request it'll likely be wrong each time. + mNeedsCacheLoad = false; + long t0 = System.currentTimeMillis(); + + Cursor cur = null; + try { + cur = mDatabaseHelper.getReadableDatabase().query(SSL_CACHE_TABLE, PROJECTION, null, + null, null, null, null); + if (cur.moveToFirst()) { + do { + String hostPort = cur.getString(SSL_CACHE_HOSTPORT_COL); + String value = cur.getString(SSL_CACHE_SESSION_COL); + + if (hostPort == null || value == null) { + continue; + } + // TODO: blob support ? + byte[] der = Base64.decodeBase64(value.getBytes()); + mExternalCache.put(hostPort, der); + } while (cur.moveToNext()); + + } + } catch (SQLException ex) { + Log.d(TAG, "Error loading SSL cached entries ", ex); + } finally { + if (cur != null) { + cur.close(); + } + if (Log.isLoggable(TAG, Log.DEBUG)) { + long t1 = System.currentTimeMillis(); + Log.d(TAG, "LOADED CACHED SSL " + (t1 - t0) + " ms"); + } + } + } + + String key = host + ":" + port; + + return mExternalCache.get(key); + } + } + + public byte[] getSessionData(byte[] id) { + // We support client side only - the cache will do nothing on client. + return null; + } + + /** Visible for testing. + */ + public static class DatabaseHelper extends SQLiteOpenHelper { + + public DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null /* factory */, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + SSL_CACHE_TABLE + " (" + + SSL_CACHE_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + + SSL_CACHE_HOSTPORT + " TEXT UNIQUE ON CONFLICT REPLACE," + + SSL_CACHE_SESSION + " TEXT," + + SSL_CACHE_TIME_SEC + " INTEGER" + + ");"); + db.execSQL("CREATE INDEX ssl_sessions_idx1 ON ssl_sessions (" + + SSL_CACHE_HOSTPORT + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + SSL_CACHE_TABLE ); + onCreate(db); + } + + } + +}
\ No newline at end of file diff --git a/tests/CoreTests/android/core/SSLPerformanceTest.java b/tests/CoreTests/android/core/SSLPerformanceTest.java new file mode 100644 index 0000000..e2bd9c5 --- /dev/null +++ b/tests/CoreTests/android/core/SSLPerformanceTest.java @@ -0,0 +1,432 @@ +/* + * 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 android.core; + +import android.test.AndroidTestCase; +import android.os.Debug; +import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache; +import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache; +import org.apache.harmony.xnet.provider.jsse.SSLContextImpl; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.impl.conn.SingleClientConnManager; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.HttpResponse; + +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionContext; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; +import java.security.cert.Certificate; +import java.security.Principal; +import java.security.KeyManagementException; +import java.util.Arrays; + +public class SSLPerformanceTest extends AndroidTestCase { + + static final byte[] SESSION_DATA = new byte[6000]; + static { + for (int i = 0; i < SESSION_DATA.length; i++) { + SESSION_DATA[i] = (byte) i; + } + } + + static final File dataDir = new File("/data/data/android.core/"); + static final File filesDir = new File(dataDir, "files"); + static final File dbDir = new File(dataDir, "databases"); + + static final String CACHE_DIR + = SSLPerformanceTest.class.getName() + "/cache"; + + static final int ITERATIONS = 10; + + public void testCreateNewEmptyDatabase() { + deleteDatabase(); + + Stopwatch stopwatch = new Stopwatch(); + + DatabaseSessionCache cache = new DatabaseSessionCache(getContext()); + cache.getSessionData("crazybob.org", 443); + + stopwatch.stop(); + } + + public void testCreateNewEmptyDirectory() throws IOException { + deleteDirectory(); + + Stopwatch stopwatch = new Stopwatch(); + + SSLClientSessionCache cache = FileClientSessionCache.usingDirectory( + getCacheDirectory()); + cache.getSessionData("crazybob.org", 443); + + stopwatch.stop(); + } + + public void testOpenDatabaseWith10Sessions() { + deleteDatabase(); + + DatabaseSessionCache cache = new DatabaseSessionCache(getContext()); + putSessionsIn(cache); + closeDatabase(); + + System.err.println("Size of ssl_sessions.db w/ 10 sessions: " + + new File(dbDir, "ssl_sessions.db").length()); + + Stopwatch stopwatch = new Stopwatch(); + + cache = new DatabaseSessionCache(getContext()); + cache.getSessionData("crazybob.org", 443); + + stopwatch.stop(); + } + + public void testOpenDirectoryWith10Sessions() throws IOException { + deleteDirectory(); + + SSLClientSessionCache cache = FileClientSessionCache.usingDirectory( + getCacheDirectory()); + putSessionsIn(cache); + closeDirectoryCache(); + + Stopwatch stopwatch = new Stopwatch(); + + cache = FileClientSessionCache.usingDirectory( + getCacheDirectory()); + cache.getSessionData("crazybob.org", 443); + + stopwatch.stop(); + } + + public void testGetSessionFromDatabase() { + deleteDatabase(); + + DatabaseSessionCache cache = new DatabaseSessionCache(getContext()); + cache.putSessionData(new FakeSession("foo"), SESSION_DATA); + closeDatabase(); + + cache = new DatabaseSessionCache(getContext()); + cache.getSessionData("crazybob.org", 443); + + Stopwatch stopwatch = new Stopwatch(); + + byte[] sessionData = cache.getSessionData("foo", 443); + + stopwatch.stop(); + + assertTrue(Arrays.equals(SESSION_DATA, sessionData)); + } + + public void testGetSessionFromDirectory() throws IOException { + deleteDirectory(); + + SSLClientSessionCache cache = FileClientSessionCache.usingDirectory( + getCacheDirectory()); + cache.putSessionData(new FakeSession("foo"), SESSION_DATA); + closeDirectoryCache(); + + cache = FileClientSessionCache.usingDirectory( + getCacheDirectory()); + cache.getSessionData("crazybob.org", 443); + + Stopwatch stopwatch = new Stopwatch(); + + byte[] sessionData = cache.getSessionData("foo", 443); + + stopwatch.stop(); + + assertTrue(Arrays.equals(SESSION_DATA, sessionData)); + } + + public void testPutSessionIntoDatabase() { + deleteDatabase(); + + DatabaseSessionCache cache = new DatabaseSessionCache(getContext()); + cache.getSessionData("crazybob.org", 443); + + Stopwatch stopwatch = new Stopwatch(); + + cache.putSessionData(new FakeSession("foo"), SESSION_DATA); + + stopwatch.stop(); + } + + public void testPutSessionIntoDirectory() throws IOException { + deleteDirectory(); + + SSLClientSessionCache cache = FileClientSessionCache.usingDirectory( + getCacheDirectory()); + cache.getSessionData("crazybob.org", 443); + + Stopwatch stopwatch = new Stopwatch(); + + cache.putSessionData(new FakeSession("foo"), SESSION_DATA); + + stopwatch.stop(); + } + + public void testEngineInit() throws IOException, KeyManagementException { + Stopwatch stopwatch = new Stopwatch(); + + new SSLContextImpl().engineInit(null, null, null); + + stopwatch.stop(); + } + + public void testWebRequestWithoutCache() throws IOException, + KeyManagementException { + SSLContextImpl sslContext = new SSLContextImpl(); + sslContext.engineInit(null, null, null); + + Stopwatch stopwatch = new Stopwatch(); + + getVerisignDotCom(sslContext); + + stopwatch.stop(); + } + + public void testWebRequestWithFileCache() throws IOException, + KeyManagementException { + deleteDirectory(); + + SSLContextImpl sslContext = new SSLContextImpl(); + sslContext.engineInit(null, null, null, + FileClientSessionCache.usingDirectory(getCacheDirectory()), + null); + + // Make sure www.google.com is in the cache. + getVerisignDotCom(sslContext); + + // Re-initialize so we hit the file cache. + sslContext.engineInit(null, null, null, + FileClientSessionCache.usingDirectory(getCacheDirectory()), + null); + + Stopwatch stopwatch = new Stopwatch(); + + getVerisignDotCom(sslContext); + + stopwatch.stop(); + } + + public void testWebRequestWithInMemoryCache() throws IOException, + KeyManagementException { + deleteDirectory(); + + SSLContextImpl sslContext = new SSLContextImpl(); + sslContext.engineInit(null, null, null); + + // Make sure www.google.com is in the cache. + getVerisignDotCom(sslContext); + + Stopwatch stopwatch = new Stopwatch(); + + getVerisignDotCom(sslContext); + + stopwatch.stop(); + } + + private void getVerisignDotCom(SSLContextImpl sslContext) + throws IOException { + SchemeRegistry schemeRegistry = new SchemeRegistry(); + schemeRegistry.register(new Scheme("https", + new SSLSocketFactory(sslContext.engineGetSocketFactory()), + 443)); + + ClientConnectionManager manager = + new SingleClientConnManager(null, schemeRegistry); + + new DefaultHttpClient(manager, null).execute( + new HttpGet("https://www.verisign.com"), + new ResponseHandler<Object>() { + public Object handleResponse(HttpResponse response) + throws ClientProtocolException, IOException { + return null; + } + }); + } + + private void putSessionsIn(SSLClientSessionCache cache) { + for (int i = 0; i < 10; i++) { + cache.putSessionData(new FakeSession("host" + i), SESSION_DATA); + } + } + + private void deleteDatabase() { + closeDatabase(); + if (!new File(dbDir, "ssl_sessions.db").delete()) { + System.err.println("Failed to delete database."); + } + } + + private void closeDatabase() { + if (DatabaseSessionCache.sDefaultDatabaseHelper != null) { + DatabaseSessionCache.sDefaultDatabaseHelper.close(); + } + DatabaseSessionCache.sDefaultDatabaseHelper = null; + DatabaseSessionCache.sHookInitializationDone = false; + DatabaseSessionCache.mNeedsCacheLoad = true; + } + + private void deleteDirectory() { + closeDirectoryCache(); + + File dir = getCacheDirectory(); + if (!dir.exists()) { + return; + } + for (File file : dir.listFiles()) { + file.delete(); + } + if (!dir.delete()) { + System.err.println("Failed to delete directory."); + } + } + + private void closeDirectoryCache() { + try { + Method reset = FileClientSessionCache.class + .getDeclaredMethod("reset"); + reset.setAccessible(true); + reset.invoke(null); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private File getCacheDirectory() { + return new File(getContext().getFilesDir(), CACHE_DIR); + } + + class Stopwatch { + { + Debug.startAllocCounting(); + } + long start = System.nanoTime(); + + void stop() { + long elapsed = (System.nanoTime() - start) / 1000; + Debug.stopAllocCounting(); + System.err.println(getName() + ": " + elapsed + "us, " + + Debug.getThreadAllocCount() + " allocations, " + + Debug.getThreadAllocSize() + " bytes"); + } + } +} + +class FakeSession implements SSLSession { + final String host; + + FakeSession(String host) { + this.host = host; + } + + public int getApplicationBufferSize() { + throw new UnsupportedOperationException(); + } + + public String getCipherSuite() { + throw new UnsupportedOperationException(); + } + + public long getCreationTime() { + throw new UnsupportedOperationException(); + } + + public byte[] getId() { + return host.getBytes(); + } + + public long getLastAccessedTime() { + throw new UnsupportedOperationException(); + } + + public Certificate[] getLocalCertificates() { + throw new UnsupportedOperationException(); + } + + public Principal getLocalPrincipal() { + throw new UnsupportedOperationException(); + } + + public int getPacketBufferSize() { + throw new UnsupportedOperationException(); + } + + public javax.security.cert.X509Certificate[] getPeerCertificateChain() { + throw new UnsupportedOperationException(); + } + + public Certificate[] getPeerCertificates() { + throw new UnsupportedOperationException(); + } + + public String getPeerHost() { + return host; + } + + public int getPeerPort() { + return 443; + } + + public Principal getPeerPrincipal() { + throw new UnsupportedOperationException(); + } + + public String getProtocol() { + throw new UnsupportedOperationException(); + } + + public SSLSessionContext getSessionContext() { + throw new UnsupportedOperationException(); + } + + public Object getValue(String name) { + throw new UnsupportedOperationException(); + } + + public String[] getValueNames() { + throw new UnsupportedOperationException(); + } + + public void invalidate() { + throw new UnsupportedOperationException(); + } + + public boolean isValid() { + throw new UnsupportedOperationException(); + } + + public void putValue(String name, Object value) { + throw new UnsupportedOperationException(); + } + + public void removeValue(String name) { + throw new UnsupportedOperationException(); + } +} diff --git a/tests/CoreTests/android/core/SSLSocketTest.java b/tests/CoreTests/android/core/SSLSocketTest.java index 922090a..088fa8c 100644 --- a/tests/CoreTests/android/core/SSLSocketTest.java +++ b/tests/CoreTests/android/core/SSLSocketTest.java @@ -19,9 +19,13 @@ package android.core; import junit.framework.TestCase; import org.apache.commons.codec.binary.Base64; +import org.apache.harmony.xnet.provider.jsse.SSLContextImpl; +import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache; +import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache; import java.io.ByteArrayInputStream; import java.io.DataInputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -29,18 +33,28 @@ import java.io.PrintWriter; import java.net.InetSocketAddress; import java.net.Socket; import java.security.KeyStore; +import java.security.KeyManagementException; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Random; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; +/** + * SSL integration tests that hit real servers. + */ public class SSLSocketTest extends TestCase { private static SSLSocketFactory clientFactory = @@ -61,13 +75,15 @@ public class SSLSocketTest extends TestCase { * @param delay The delay after each request (in seconds). * @throws IOException When a problem occurs. */ - private void fetch(String host, int port, boolean secure, String path, - int outerLoop, int innerLoop, int delay, int timeout) throws IOException { + private void fetch(SSLSocketFactory socketFactory, String host, int port, + boolean secure, String path, int outerLoop, int innerLoop, + int delay, int timeout) throws IOException { InetSocketAddress address = new InetSocketAddress(host, port); for (int i = 0; i < outerLoop; i++) { // Connect to the remote host - Socket socket = secure ? clientFactory.createSocket() : new Socket(); + Socket socket = secure ? socketFactory.createSocket() + : new Socket(); if (timeout >= 0) { socket.setKeepAlive(true); socket.setSoTimeout(timeout * 1000); @@ -159,6 +175,16 @@ public class SSLSocketTest extends TestCase { } /** + * Invokes fetch() with the default socket factory. + */ + private void fetch(String host, int port, boolean secure, String path, + int outerLoop, int innerLoop, + int delay, int timeout) throws IOException { + fetch(clientFactory, host, port, secure, path, outerLoop, innerLoop, + delay, timeout); + } + + /** * Does a single request for each of the hosts. Consumes the response. * * @throws IOException If a problem occurs. @@ -619,13 +645,17 @@ public class SSLSocketTest extends TestCase { public void run() { try { - KeyManager[] keyManagers = provideKeys ? getKeyManagers(SERVER_KEYS_BKS) : null; - TrustManager[] trustManagers = new TrustManager[] { trustManager }; + KeyManager[] keyManagers = provideKeys + ? getKeyManagers(SERVER_KEYS_BKS) : null; + TrustManager[] trustManagers = new TrustManager[] { + trustManager }; SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagers, trustManagers, null); - SSLServerSocket serverSocket = (SSLServerSocket)sslContext.getServerSocketFactory().createServerSocket(); + SSLServerSocket serverSocket + = (SSLServerSocket) sslContext.getServerSocketFactory() + .createServerSocket(); if (clientAuth == CLIENT_AUTH_WANTED) { serverSocket.setWantClientAuth(true); @@ -637,14 +667,15 @@ public class SSLSocketTest extends TestCase { serverSocket.bind(new InetSocketAddress(port)); - SSLSocket clientSocket = (SSLSocket)serverSocket.accept(); + SSLSocket clientSocket = (SSLSocket) serverSocket.accept(); InputStream stream = clientSocket.getInputStream(); for (int i = 0; i < 256; i++) { int j = stream.read(); if (i != j) { - throw new RuntimeException("Error reading socket, expected " + i + ", got " + j); + throw new RuntimeException("Error reading socket," + + " expected " + i + ", got " + j); } } @@ -690,13 +721,16 @@ public class SSLSocketTest extends TestCase { public void run() { try { - KeyManager[] keyManagers = provideKeys ? getKeyManagers(CLIENT_KEYS_BKS) : null; - TrustManager[] trustManagers = new TrustManager[] { trustManager }; + KeyManager[] keyManagers = provideKeys + ? getKeyManagers(CLIENT_KEYS_BKS) : null; + TrustManager[] trustManagers = new TrustManager[] { + trustManager }; SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagers, trustManagers, null); - SSLSocket socket = (SSLSocket)sslContext.getSocketFactory().createSocket(); + SSLSocket socket = (SSLSocket) sslContext.getSocketFactory() + .createSocket(); socket.connect(new InetSocketAddress(port)); socket.startHandshake(); @@ -867,6 +901,189 @@ public class SSLSocketTest extends TestCase { fail("SSL handshake should have failed."); } } - - + + /** + * Tests our in-memory and persistent caching support. + */ + public void testClientSessionCaching() throws IOException, + KeyManagementException { + SSLContextImpl context = new SSLContextImpl(); + + // Cache size = 2. + FakeClientSessionCache fakeCache = new FakeClientSessionCache(); + context.engineInit(null, null, null, fakeCache, null); + SSLSocketFactory socketFactory = context.engineGetSocketFactory(); + context.engineGetClientSessionContext().setSessionCacheSize(2); + makeRequests(socketFactory); + List<String> smallCacheOps = Arrays.asList( + "get www.fortify.net", + "put www.fortify.net", + "get www.paypal.com", + "put www.paypal.com", + "get www.yellownet.ch", + "put www.yellownet.ch", + + // At this point, all in-memory cache requests should miss, + // but the sessions will still be in the persistent cache. + "get www.fortify.net", + "get www.paypal.com", + "get www.yellownet.ch" + ); + assertEquals(smallCacheOps, fakeCache.ops); + + // Cache size = 3. + fakeCache = new FakeClientSessionCache(); + context.engineInit(null, null, null, fakeCache, null); + socketFactory = context.engineGetSocketFactory(); + context.engineGetClientSessionContext().setSessionCacheSize(3); + makeRequests(socketFactory); + List<String> bigCacheOps = Arrays.asList( + "get www.fortify.net", + "put www.fortify.net", + "get www.paypal.com", + "put www.paypal.com", + "get www.yellownet.ch", + "put www.yellownet.ch" + + // At this point, all results should be in the in-memory + // cache, and the persistent cache shouldn't be hit anymore. + ); + assertEquals(bigCacheOps, fakeCache.ops); + + // Cache size = 4. + fakeCache = new FakeClientSessionCache(); + context.engineInit(null, null, null, fakeCache, null); + socketFactory = context.engineGetSocketFactory(); + context.engineGetClientSessionContext().setSessionCacheSize(4); + makeRequests(socketFactory); + assertEquals(bigCacheOps, fakeCache.ops); + } + + /** + * Executes sequence of requests twice using given socket factory. + */ + private void makeRequests(SSLSocketFactory socketFactory) + throws IOException { + for (int i = 0; i < 2; i++) { + fetch(socketFactory, "www.fortify.net", 443, true, "/sslcheck.html", + 1, 1, 0, 60); + fetch(socketFactory, "www.paypal.com", 443, true, "/", + 1, 1, 0, 60); + fetch(socketFactory, "www.yellownet.ch", 443, true, "/", + 1, 1, 0, 60); + } + } + + /** + * Fake in the sense that it doesn't actually persist anything. + */ + static class FakeClientSessionCache implements SSLClientSessionCache { + + List<String> ops = new ArrayList<String>(); + Map<String, byte[]> sessions = new HashMap<String, byte[]>(); + + public byte[] getSessionData(String host, int port) { + ops.add("get " + host); + return sessions.get(host); + } + + public void putSessionData(SSLSession session, byte[] sessionData) { + String host = session.getPeerHost(); + System.err.println("length: " + sessionData.length); + ops.add("put " + host); + sessions.put(host, sessionData); + } + } + + public void testFileBasedClientSessionCache() throws IOException, + KeyManagementException { + SSLContextImpl context = new SSLContextImpl(); + String tmpDir = System.getProperty("java.io.tmpdir"); + if (tmpDir == null) { + fail("Please set 'java.io.tmpdir' system property."); + } + File cacheDir = new File(tmpDir + + "/" + SSLSocketTest.class.getName() + "/cache"); + deleteDir(cacheDir); + SSLClientSessionCache fileCache + = FileClientSessionCache.usingDirectory(cacheDir); + try { + ClientSessionCacheProxy cacheProxy + = new ClientSessionCacheProxy(fileCache); + context.engineInit(null, null, null, cacheProxy, null); + SSLSocketFactory socketFactory = context.engineGetSocketFactory(); + context.engineGetClientSessionContext().setSessionCacheSize(1); + makeRequests(socketFactory); + List<String> expected = Arrays.asList( + "unsuccessful get www.fortify.net", + "put www.fortify.net", + "unsuccessful get www.paypal.com", + "put www.paypal.com", + "unsuccessful get www.yellownet.ch", + "put www.yellownet.ch", + + // At this point, all in-memory cache requests should miss, + // but the sessions will still be in the persistent cache. + "successful get www.fortify.net", + "successful get www.paypal.com", + "successful get www.yellownet.ch" + ); + assertEquals(expected, cacheProxy.ops); + + // Try again now that file-based cache is populated. + fileCache = FileClientSessionCache.usingDirectory(cacheDir); + cacheProxy = new ClientSessionCacheProxy(fileCache); + context.engineInit(null, null, null, cacheProxy, null); + socketFactory = context.engineGetSocketFactory(); + context.engineGetClientSessionContext().setSessionCacheSize(1); + makeRequests(socketFactory); + expected = Arrays.asList( + "successful get www.fortify.net", + "successful get www.paypal.com", + "successful get www.yellownet.ch", + "successful get www.fortify.net", + "successful get www.paypal.com", + "successful get www.yellownet.ch" + ); + assertEquals(expected, cacheProxy.ops); + } finally { + deleteDir(cacheDir); + } + } + + private static void deleteDir(File directory) { + if (!directory.exists()) { + return; + } + for (File file : directory.listFiles()) { + file.delete(); + } + directory.delete(); + } + + static class ClientSessionCacheProxy implements SSLClientSessionCache { + + final SSLClientSessionCache delegate; + final List<String> ops = new ArrayList<String>(); + + ClientSessionCacheProxy(SSLClientSessionCache delegate) { + this.delegate = delegate; + } + + public byte[] getSessionData(String host, int port) { + byte[] sessionData = delegate.getSessionData(host, port); + ops.add((sessionData == null ? "unsuccessful" : "successful") + + " get " + host); + return sessionData; + } + + public void putSessionData(SSLSession session, byte[] sessionData) { + delegate.putSessionData(session, sessionData); + ops.add("put " + session.getPeerHost()); + } + } + + public static void main(String[] args) throws KeyManagementException, IOException { + new SSLSocketTest().testFileBasedClientSessionCache(); + } } diff --git a/tests/CoreTests/android/location/LocationManagerProximityTest.java b/tests/CoreTests/android/location/LocationManagerProximityTest.java index 5f62983..e1501e3 100644 --- a/tests/CoreTests/android/location/LocationManagerProximityTest.java +++ b/tests/CoreTests/android/location/LocationManagerProximityTest.java @@ -23,6 +23,7 @@ import android.content.IntentFilter; import android.location.Criteria; import android.location.Location; import android.location.LocationManager; +import android.provider.Settings; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.MediumTest; import android.util.Log; @@ -33,8 +34,10 @@ import android.util.Log; * TODO: add tests for more scenarios * * To run: - * adb shell am instrument -e class com.google.android.mapstests.api.LocationProximityTest \ - * -w com.google.android.mapstests/.MapInstrumentationTestRunner + * adb shell am instrument -e class android.location.LocationManagerProximityTest \ + * -w android.core/android.test.InstrumentationTestRunner + * + * This test requires that the "Allow mock locations" setting be enabled * */ @MediumTest @@ -46,8 +49,6 @@ public class LocationManagerProximityTest extends AndroidTestCase { private LocationManager mLocationManager; private PendingIntent mPendingIntent; private TestIntentReceiver mIntentReceiver; - private String mOriginalAllowedProviders; - private int mOriginalMocksAllowed; private static final String LOG_TAG = "LocationProximityTest"; @@ -60,27 +61,13 @@ public class LocationManagerProximityTest extends AndroidTestCase { @Override protected void setUp() throws Exception { super.setUp(); - - // allow mock locations - mOriginalMocksAllowed = - android.provider.Settings.Secure.getInt(getContext().getContentResolver(), - android.provider.Settings.Secure.ALLOW_MOCK_LOCATION, 0); - - android.provider.Settings.Secure.putInt(getContext().getContentResolver(), - android.provider.Settings.Secure.ALLOW_MOCK_LOCATION, 1); - - mOriginalAllowedProviders = - android.provider.Settings.Secure.getString( - getContext().getContentResolver(), - android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED); - - // ensure 'only' the mock provider is enabled - // need to do this so the proximity listener does not ignore the mock - // updates in favor of gps updates - android.provider.Settings.Secure.putString( - getContext().getContentResolver(), - android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - PROVIDER_NAME); + + // test that mock locations are allowed so a more descriptive error message can be logged + if (Settings.Secure.getInt(getContext().getContentResolver(), + Settings.Secure.ALLOW_MOCK_LOCATION, 0) == 0) { + fail("Mock locations are currently disabled in Settings - this test requires " + + "mock locations"); + } mLocationManager = (LocationManager) getContext(). getSystemService(Context.LOCATION_SERVICE); @@ -109,18 +96,6 @@ public class LocationManagerProximityTest extends AndroidTestCase { if (mIntentReceiver != null) { getContext().unregisterReceiver(mIntentReceiver); } - - android.provider.Settings.Secure.putInt(getContext().getContentResolver(), - android.provider.Settings.Secure.ALLOW_MOCK_LOCATION, mOriginalMocksAllowed); - - if (mOriginalAllowedProviders != null) { - // restore original settings - android.provider.Settings.Secure.putString( - getContext().getContentResolver(), - android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - mOriginalAllowedProviders); - mLocationManager.updateProviders(); - } } /** diff --git a/tests/CoreTests/run_core_test.sh b/tests/CoreTests/run_core_test.sh index 1fc3348..ffa31ed 100755 --- a/tests/CoreTests/run_core_test.sh +++ b/tests/CoreTests/run_core_test.sh @@ -1,4 +1,6 @@ framework=/system/framework bpath=$framework/core.jar:$framework/ext.jar:$framework/framework.jar:$framework/android.test.runner.jar -adb shell exec dalvikvm -Xbootclasspath:$bpath -cp system/app/CoreTests.apk \ +adb shell exec dalvikvm -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=3001 \ + -Xbootclasspath:$bpath -cp /data/app/android.core.apk \ + -Djava.io.tmpdir=/sdcard/tmp \ com.android.internal.util.WithFramework junit.textui.TestRunner $* diff --git a/tests/ImfTest/src/com/android/imftest/samples/InputTypeActivity.java b/tests/ImfTest/src/com/android/imftest/samples/InputTypeActivity.java index 6c71e86..17f6bdc 100755 --- a/tests/ImfTest/src/com/android/imftest/samples/InputTypeActivity.java +++ b/tests/ImfTest/src/com/android/imftest/samples/InputTypeActivity.java @@ -83,10 +83,6 @@ public class InputTypeActivity extends Activity { mLayout.addView(buildEntryView(EditorInfo.TYPE_CLASS_TEXT|EditorInfo.TYPE_TEXT_VARIATION_NORMAL|EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT, R.string.auto_correct_edit_text_label)); - /* Normal Edit Text w/Search Flag*/ - mLayout.addView(buildEntryView(EditorInfo.TYPE_CLASS_TEXT|EditorInfo.TYPE_TEXT_VARIATION_NORMAL|EditorInfo.TYPE_TEXT_FLAG_SEARCH, - R.string.search_edit_text_label)); - /* Uri Edit Text */ mLayout.addView(buildEntryView(EditorInfo.TYPE_CLASS_TEXT|EditorInfo.TYPE_TEXT_VARIATION_URI, R.string.uri_edit_text_label)); diff --git a/tests/gadgets/GadgetProviderTest/res/layout/test_gadget.xml b/tests/gadgets/GadgetProviderTest/res/layout/test_gadget.xml index 4d483c7..e0a416e 100644 --- a/tests/gadgets/GadgetProviderTest/res/layout/test_gadget.xml +++ b/tests/gadgets/GadgetProviderTest/res/layout/test_gadget.xml @@ -16,8 +16,11 @@ <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/oh_hai_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="150dp" + android:layout_height="150dp" android:text="@string/oh_hai" + android:background="#8fff" + android:textColor="#000" + android:textStyle="bold" /> diff --git a/tests/gadgets/GadgetProviderTest/res/xml/gadget_info.xml b/tests/gadgets/GadgetProviderTest/res/xml/gadget_info.xml index 0fc7812..33cc2e3 100644 --- a/tests/gadgets/GadgetProviderTest/res/xml/gadget_info.xml +++ b/tests/gadgets/GadgetProviderTest/res/xml/gadget_info.xml @@ -1,7 +1,7 @@ <gadget-provider xmlns:android="http://schemas.android.com/apk/res/android" - android:minWidth="40dp" - android:minHeight="30dp" - android:updatePeriodMillis="60000" + android:minWidth="150dp" + android:minHeight="150dp" + android:updatePeriodMillis="2000" android:initialLayout="@layout/test_gadget" > </gadget-provider> |