From c9a04f094a045eee33a4dabeea86c4d8f314dce8 Mon Sep 17 00:00:00 2001 From: hoisie Date: Fri, 3 Dec 2021 12:31:50 -0800 Subject: [PATCH] Update platformStrError to work in Windows PiperOrigin-RevId: 413996788 --- .github/workflows/build_native_runtime.yml | 12 +- .github/workflows/tests.yml | 8 + nativeruntime/build.gradle | 4 +- nativeruntime/cpp/CMakeLists.txt | 9 +- .../include/nativehelper/JNIHelp.h | 28 +- .../ContentProviderControllerTest.java | 39 +- .../robolectric/shadows/SQLiteCursorTest.java | 2 + .../shadows/SQLiteDatabaseTest.java | 47 +- .../shadows/SQLiteOpenHelperTest.java | 11 +- .../shadows/SQLiteQueryBuilderTest.java | 16 +- .../shadows/SQLiteStatementTest.java | 1 + .../shadows/ShadowAbstractCursorTest.java | 8 +- .../shadows/ShadowActivityTest.java | 6 +- .../ShadowBitmapRegionDecoderTest.java | 40 +- .../shadows/ShadowBugreportManagerTest.java | 18 +- .../shadows/ShadowContentResolverTest.java | 5 +- .../shadows/ShadowCursorAdapterTest.java | 15 +- .../shadows/ShadowCursorWindowTest.java | 5 + .../shadows/ShadowHardwareBufferTest.java | 22 +- .../shadows/ShadowMatrixCursorTest.java | 31 +- .../shadows/ShadowMediaMuxerTest.java | 1 + .../shadows/ShadowMediaPlayerTest.java | 11 +- .../shadows/ShadowMergeCursorTest.java | 25 +- .../ShadowParcelFileDescriptorTest.java | 48 +- .../shadows/ShadowRenderNodeTest.java | 1078 +++++++++++++++++ .../shadows/ShadowResourcesTest.java | 13 +- .../ShadowSimpleCursorAdapterTest.java | 51 +- .../shadows/ShadowUsbManagerTest.java | 7 +- .../shadows/ShadowVibratorTest.java | 86 ++ .../shadows/ShadowWallpaperManagerTest.java | 10 +- .../shadows/ShadowWifiAwareManagerTest.java | 23 +- .../shadows/ShadowDatePickerDialog.java | 19 - .../robolectric/shadows/ShadowRenderNode.java | 164 +++ .../shadows/ShadowRenderNodeQ.java | 164 +++ .../shadows/ShadowSystemVibrator.java | 6 +- .../robolectric/shadows/ShadowVibrator.java | 29 + 36 files changed, 1881 insertions(+), 181 deletions(-) create mode 100644 robolectric/src/test/java/org/robolectric/shadows/ShadowRenderNodeTest.java diff --git a/.github/workflows/build_native_runtime.yml b/.github/workflows/build_native_runtime.yml index 2d4f95e6df8..d403062a99c 100644 --- a/.github/workflows/build_native_runtime.yml +++ b/.github/workflows/build_native_runtime.yml @@ -32,6 +32,14 @@ jobs: with: java-version: 11.0.8 + - name: Install Ninja (Mac OS) + if: runner.os =='macOS' + run: brew install ninja + + - name: Install Ninja (Linux) + if: runner.os =='Linux' + run: sudo apt-get install ninja-build + - name: Cache ICU build output id: cache-icu uses: actions/cache@v2 @@ -61,8 +69,8 @@ jobs: run: | mkdir build cd build - ICU_ROOT_DIR=$HOME/icu-bin cmake -B . -S ../nativeruntime/cpp - make + ICU_ROOT_DIR=$HOME/icu-bin cmake -B . -S ../nativeruntime/cpp -G Ninja + ninja - name: Rename libnativeruntime for Linux if: runner.os == 'Linux' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index da64f19bf8b..42624559c09 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -52,6 +52,10 @@ jobs: make -j4 make install + - name: Install Ninja (Linux) + if: runner.os =='Linux' + run: sudo apt-get install ninja-build + - name: Build run: ICU_ROOT_DIR=$HOME/icu-bin SKIP_ERRORPRONE=true SKIP_JAVADOC=true ./gradlew clean assemble testClasses --parallel --stacktrace --no-watch-fs @@ -93,6 +97,10 @@ jobs: path: ~/icu-bin key: ${{ runner.os }}-${{env.CPU_ARCH}}-icu-${{ hashFiles('nativeruntime/external/icu/**') }} + - name: Install Ninja + if: runner.os =='Linux' + run: sudo apt-get install ninja-build + - name: Run unit tests run: | ICU_ROOT_DIR=$HOME/icu-bin SKIP_ERRORPRONE=true SKIP_JAVADOC=true ./gradlew test --info --stacktrace --continue \ diff --git a/nativeruntime/build.gradle b/nativeruntime/build.gradle index 1c3262488e9..9a7ef90de47 100644 --- a/nativeruntime/build.gradle +++ b/nativeruntime/build.gradle @@ -29,7 +29,7 @@ task cmakeNativeRuntime { mkdir "$buildDir/cpp" exec { workingDir "$buildDir/cpp" - commandLine 'cmake', "-B", ".", "-S","$projectDir/cpp/" + commandLine 'cmake', "-B", ".", "-S","$projectDir/cpp/", "-G", "Ninja" } } } @@ -80,7 +80,7 @@ task makeNativeRuntime { doLast { exec { workingDir "$buildDir/cpp" - commandLine 'make' + commandLine 'ninja' } } } diff --git a/nativeruntime/cpp/CMakeLists.txt b/nativeruntime/cpp/CMakeLists.txt index 85105d8b3ee..d45664f977d 100644 --- a/nativeruntime/cpp/CMakeLists.txt +++ b/nativeruntime/cpp/CMakeLists.txt @@ -3,11 +3,6 @@ cmake_minimum_required(VERSION 3.10) # This is needed to ensure that static libraries can be linked into shared libraries. set(CMAKE_POSITION_INDEPENDENT_CODE ON) -# Building the nativeruntime does not work with GCC due to libstddc++ linker errors. -# TODO: figure out which linker args are needed to build with GCC. -set(CMAKE_C_COMPILER "clang") -set(CMAKE_CXX_COMPILER "clang++") - # Some libutils headers require C++17 set (CMAKE_CXX_STANDARD 17) @@ -100,6 +95,8 @@ target_link_libraries(androidsqlite ${STATIC_ICUI18N_LIBRARY} ${STATIC_ICUUC_LIBRARY} ${STATIC_ICUDATA_LIBRARY} + -ldl + -lpthread ) include_directories(${JNI_INCLUDE_DIRS}) @@ -142,8 +139,6 @@ if (CMAKE_HOST_SYSTEM_NAME MATCHES "Linux") target_link_libraries(nativeruntime -static-libgcc -static-libstdc++ - -ldl - -lpthread -Wl,--no-undefined # print an error if there are any undefined symbols ) endif() diff --git a/nativeruntime/cpp/libnativehelper/include/nativehelper/JNIHelp.h b/nativeruntime/cpp/libnativehelper/include/nativehelper/JNIHelp.h index cf03f66e15a..1dd44b60268 100644 --- a/nativeruntime/cpp/libnativehelper/include/nativehelper/JNIHelp.h +++ b/nativeruntime/cpp/libnativehelper/include/nativehelper/JNIHelp.h @@ -89,26 +89,22 @@ struct [[maybe_unused]] ExpandableString { return ExpandableStringAppend(s, text); } -[[maybe_unused]] inline char* safe_strerror( - char* (*strerror_r_method)(int, char*, size_t), int errnum, char* buf, - size_t buflen) { - return strerror_r_method(errnum, buf, buflen); -} - -[[maybe_unused]] inline char* safe_strerror(int (*strerror_r_method)(int, char*, - size_t), - int errnum, char* buf, - size_t buflen) { - int rc = strerror_r_method(errnum, buf, buflen); +[[maybe_unused]] inline const char* platformStrError(int errnum, char* buf, + size_t buflen) { +#ifdef _WIN32 + strerror_s(buf, buflen, errnum); + return buf; +#elif defined(__USE_GNU) + // char *strerror_r(int errnum, char *buf, size_t buflen); /* GNU-specific */ + return strerror_r(errnum, buf, buflen); +#else + // int strerror_r(int errnum, char *buf, size_t buflen); /* XSI-compliant */ + int rc = strerror_r(errnum, buf, buflen); if (rc != 0) { snprintf(buf, buflen, "errno %d", errnum); } return buf; -} - -[[maybe_unused]] static const char* platformStrError(int errnum, char* buf, - size_t buflen) { - return safe_strerror(strerror_r, errnum, buf, buflen); +#endif } [[maybe_unused]] static jmethodID FindMethod(JNIEnv* env, const char* className, diff --git a/robolectric/src/test/java/org/robolectric/android/controller/ContentProviderControllerTest.java b/robolectric/src/test/java/org/robolectric/android/controller/ContentProviderControllerTest.java index c6c1641d9e0..9cdff632c4f 100644 --- a/robolectric/src/test/java/org/robolectric/android/controller/ContentProviderControllerTest.java +++ b/robolectric/src/test/java/org/robolectric/android/controller/ContentProviderControllerTest.java @@ -1,6 +1,7 @@ package org.robolectric.android.controller; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import android.app.Application; import android.content.ContentProviderClient; @@ -8,12 +9,14 @@ import android.content.pm.PathPermission; import android.content.pm.ProviderInfo; import android.net.Uri; +import android.os.Build; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; +import org.robolectric.RuntimeEnvironment; import org.robolectric.shadows.testing.TestContentProvider1; import org.robolectric.shadows.testing.TestContentProvider3And4; @@ -42,7 +45,7 @@ public void shouldInitializeFromManifestProviderInfo() throws Exception { assertThat(myContentProvider.getReadPermission()).isEqualTo("READ_PERMISSION"); assertThat(myContentProvider.getWritePermission()).isEqualTo("WRITE_PERMISSION"); - assertThat(myContentProvider.getPathPermissions()).asList().hasSize(1); + assertThat(myContentProvider.getPathPermissions()).hasLength(1); PathPermission pathPermission = myContentProvider.getPathPermissions()[0]; assertThat(pathPermission.getPath()).isEqualTo("/path/*"); assertThat(pathPermission.getType()).isEqualTo(PathPermission.PATTERN_SIMPLE_GLOB); @@ -55,10 +58,10 @@ public void shouldRegisterWithContentResolver() throws Exception { controller.create().get(); ContentProviderClient client = - contentResolver.acquireContentProviderClient( - "org.robolectric.authority1"); - client.query(Uri.parse("something"), new String[]{"title"}, "*", new String[]{}, "created"); + contentResolver.acquireContentProviderClient("org.robolectric.authority1"); + client.query(Uri.parse("something"), new String[] {"title"}, "*", new String[] {}, "created"); assertThat(controller.get().transcript).containsExactly("onCreate", "query for something"); + close(client); } @Test @@ -70,6 +73,7 @@ public void shouldResolveProvidersWithMultipleAuthorities() throws Exception { contentResolver.acquireContentProviderClient("org.robolectric.authority3"); client.query(Uri.parse("something"), new String[] {"title"}, "*", new String[] {}, "created"); assertThat(contentProvider.transcript).containsExactly("onCreate", "query for something"); + close(client); } @Test @@ -98,9 +102,11 @@ public void withoutManifest_shouldRegisterWithContentResolver() throws Exception providerInfo.authority = "some-authority"; controller.create(providerInfo); - ContentProviderClient client = contentResolver.acquireContentProviderClient(providerInfo.authority); - client.query(Uri.parse("something"), new String[]{"title"}, "*", new String[]{}, "created"); + ContentProviderClient client = + contentResolver.acquireContentProviderClient(providerInfo.authority); + client.query(Uri.parse("something"), new String[] {"title"}, "*", new String[] {}, "created"); assertThat(controller.get().transcript).containsExactly("onCreate", "query for something"); + close(client); } @Test @@ -111,11 +117,17 @@ public void contentProviderShouldBeCreatedBeforeBeingRegistered() throws Excepti ContentProviderClient contentProviderClient = contentResolver.acquireContentProviderClient("x-authority"); assertThat(contentProviderClient.getLocalContentProvider()).isSameInstanceAs(xContentProvider); + close(contentProviderClient); } - @Test(expected = IllegalArgumentException.class) + @Test public void createContentProvider_nullAuthority() throws Exception { - Robolectric.buildContentProvider(XContentProvider.class).create(new ProviderInfo()).get(); + assertThrows( + IllegalArgumentException.class, + () -> + Robolectric.buildContentProvider(XContentProvider.class) + .create(new ProviderInfo()) + .get()); } static class XContentProvider extends TestContentProvider1 { @@ -129,9 +141,20 @@ public boolean onCreate() { contentProviderClient == null ? "x-authority" + " not registered" + " yet" : "x-authority" + " is registered"); + if (contentProviderClient != null) { + close(contentProviderClient); + } return false; } } static class NotInManifestContentProvider extends TestContentProvider1 {} + + private static void close(ContentProviderClient client) { + if (RuntimeEnvironment.getApiLevel() > Build.VERSION_CODES.M) { + client.close(); + } else { + client.release(); + } + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/SQLiteCursorTest.java b/robolectric/src/test/java/org/robolectric/shadows/SQLiteCursorTest.java index c862afe2a94..c648eb30b5d 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/SQLiteCursorTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/SQLiteCursorTest.java @@ -39,6 +39,7 @@ public void setUp() throws Exception { @After public void tearDown() { database.close(); + cursor.close(); } @Test @@ -482,6 +483,7 @@ private Cursor createCursor() { private void setupEmptyResult() { database.execSQL("DELETE FROM table_name;"); + cursor.close(); cursor = createCursor(); } diff --git a/robolectric/src/test/java/org/robolectric/shadows/SQLiteDatabaseTest.java b/robolectric/src/test/java/org/robolectric/shadows/SQLiteDatabaseTest.java index 0cc99dbc62b..b1c496cc01c 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/SQLiteDatabaseTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/SQLiteDatabaseTest.java @@ -122,6 +122,7 @@ public void testInsertAndQuery() { assertThat(stringValueFromDatabase).isEqualTo(stringColumnValue); assertThat(byteValueFromDatabase).isEqualTo(byteColumnValue); + cursor.close(); } @Test @@ -146,6 +147,7 @@ public void testInsertAndRawQuery() { assertThat(stringValueFromDatabase).isEqualTo(stringColumnValue); assertThat(byteValueFromDatabase).isEqualTo(byteColumnValue); + cursor.close(); } @Test(expected = android.database.SQLException.class) @@ -173,6 +175,7 @@ public void testInsertOrThrow() { String stringValueFromDatabase = cursor.getString(1); assertThat(stringValueFromDatabase).isEqualTo(stringColumnValue); assertThat(byteValueFromDatabase).isEqualTo(byteColumnValue); + cursor.close(); } @Test(expected = IllegalArgumentException.class) @@ -194,12 +197,14 @@ public void testRawQueryCountWithOneArgument() { "select second_column, first_column from rawtable WHERE" + " `id` = ?", new String[] {"1"}); assertThat(cursor.getCount()).isEqualTo(1); + cursor.close(); } @Test public void testRawQueryCountWithNullArgs() { Cursor cursor = database.rawQuery("select second_column, first_column from rawtable", null); assertThat(cursor.getCount()).isEqualTo(2); + cursor.close(); } @Test @@ -208,6 +213,7 @@ public void testRawQueryCountWithEmptyArguments() { database.rawQuery( "select second_column, first_column" + " from" + " rawtable", new String[] {}); assertThat(cursor.getCount()).isEqualTo(2); + cursor.close(); } @Test(expected = IllegalArgumentException.class) @@ -235,6 +241,7 @@ public void testEmptyTable() { null); assertThat(cursor.moveToFirst()).isFalse(); + cursor.close(); } @Test @@ -325,6 +332,7 @@ public void testUpdate() { assertThat(cursor.getCount()).isEqualTo(1); assertIdAndName(cursor, 1234L, "Buster"); + cursor.close(); } @Test @@ -339,6 +347,7 @@ public void testUpdateNoMatch() { assertThat(cursor.getCount()).isEqualTo(1); assertIdAndName(cursor, 1234L, "Chuck"); + cursor.close(); } @Test @@ -361,6 +370,7 @@ public void testUpdateAll() { assertThat(cursor.moveToNext()).isFalse(); assertThat(cursor.isAfterLast()).isTrue(); assertThat(cursor.moveToNext()).isFalse(); + cursor.close(); } @Test @@ -402,6 +412,7 @@ public void testExecSQL() { assertThat(cursor).isNotNull(); assertThat(cursor.moveToNext()).isTrue(); assertThat(cursor.getInt(0)).isEqualTo(1); + cursor.close(); cursor = database.rawQuery("SELECT * FROM table_name", null); assertThat(cursor).isNotNull(); @@ -409,6 +420,7 @@ public void testExecSQL() { assertThat(cursor.getInt(cursor.getColumnIndex("id"))).isEqualTo(1234); assertThat(cursor.getString(cursor.getColumnIndex("name"))).isEqualTo("Chuck"); + cursor.close(); } @Test @@ -429,6 +441,7 @@ public void testExecSQLParams() { assertThat(cursor).isNotNull(); assertThat(cursor.moveToNext()).isTrue(); assertThat(cursor.getInt(0)).isEqualTo(2); + cursor.close(); cursor = database.rawQuery("SELECT `id`, `name` ,`lastUsed` FROM `routine`", null); assertThat(cursor).isNotNull(); @@ -443,6 +456,7 @@ public void testExecSQLParams() { assertThat(cursor.getInt(cursor.getColumnIndex("id"))).isEqualTo(2); assertThat(cursor.getInt(cursor.getColumnIndex("lastUsed"))).isEqualTo(1); assertThat(cursor.getString(cursor.getColumnIndex("name"))).isEqualTo("Bench Press"); + cursor.close(); } @Test(expected = SQLiteException.class) @@ -487,6 +501,7 @@ public void testExecSQLInsertNull() { int nameIndex = cursor.getColumnIndex("name"); assertThat(cursor.getString(nameIndex)).isEqualTo(name); assertThat(cursor.getString(firstIndex)).isEqualTo(null); + cursor.close(); } @Test @@ -525,6 +540,7 @@ public void shouldStoreGreatBigHonkingIntegersCorrectly() { database.query("table_name", new String[] {"big_int"}, null, null, null, null, null); assertThat(cursor.moveToFirst()).isTrue(); assertEquals(1234567890123456789L, cursor.getLong(0)); + cursor.close(); } @Test @@ -537,6 +553,7 @@ public void testSuccessTransaction() { Cursor cursor = database.rawQuery("SELECT COUNT(*) FROM table_name", null); assertThat(cursor.moveToNext()).isTrue(); assertThat(cursor.getInt(0)).isEqualTo(1); + cursor.close(); } @Test @@ -557,6 +574,7 @@ public void testFailureTransaction() { cursor = database.rawQuery(select, null); assertThat(cursor.moveToNext()).isTrue(); assertThat(cursor.getInt(0)).isEqualTo(0); + cursor.close(); } @Test @@ -573,6 +591,7 @@ public void testSuccessNestedTransaction() { Cursor cursor = database.rawQuery("SELECT COUNT(*) FROM table_name", null); assertThat(cursor.moveToNext()).isTrue(); assertThat(cursor.getInt(0)).isEqualTo(2); + cursor.close(); } @Test @@ -588,6 +607,7 @@ public void testFailureNestedTransaction() { Cursor cursor = database.rawQuery("SELECT COUNT(*) FROM table_name", null); assertThat(cursor.moveToNext()).isTrue(); assertThat(cursor.getInt(0)).isEqualTo(0); + cursor.close(); } @Test @@ -600,6 +620,8 @@ public void testTransactionAlreadySuccessful() { } catch (IllegalStateException e) { assertThat(e.getMessage()).contains("transaction"); assertThat(e.getMessage()).contains("successful"); + } finally { + database.endTransaction(); } } @@ -629,6 +651,7 @@ public void testReplace() { assertThat(cursor.moveToNext()).isTrue(); assertThat(cursor.getString(cursor.getColumnIndex("name"))).isEqualTo("Norris"); + cursor.close(); } @Test @@ -657,21 +680,26 @@ public void testReplaceIsReplacing() { assertThat(secondId).isEqualTo(id); assertThat(firstCursor.getString(0)).isEqualTo(stringValueA); assertThat(secondCursor.getString(0)).isEqualTo(stringValueB); + firstCursor.close(); + secondCursor.close(); } @Test public void shouldCreateDefaultCursorFactoryWhenNullFactoryPassedToRawQuery() { - database.rawQueryWithFactory(null, ANY_VALID_SQL, null, null); + Cursor cursor = database.rawQueryWithFactory(null, ANY_VALID_SQL, null, null); + cursor.close(); } @Test public void shouldCreateDefaultCursorFactoryWhenNullFactoryPassedToQuery() { - database.queryWithFactory(null, false, "table_name", null, null, null, null, null, null, null); + Cursor cursor = + database.queryWithFactory( + null, false, "table_name", null, null, null, null, null, null, null); + cursor.close(); } @Test public void shouldOpenExistingDatabaseFromFileSystemIfFileExists() { - database.close(); SQLiteDatabase db = @@ -679,6 +707,7 @@ public void shouldOpenExistingDatabaseFromFileSystemIfFileExists() { Cursor c = db.rawQuery("select * from rawtable", null); assertThat(c).isNotNull(); assertThat(c.getCount()).isEqualTo(2); + c.close(); assertThat(db.isOpen()).isTrue(); db.close(); assertThat(db.isOpen()).isFalse(); @@ -687,6 +716,7 @@ public void shouldOpenExistingDatabaseFromFileSystemIfFileExists() { SQLiteDatabase.openDatabase(databasePath.getAbsolutePath(), null, OPEN_READWRITE); assertThat(reopened).isNotSameInstanceAs(db); assertThat(reopened.isOpen()).isTrue(); + reopened.close(); } @Test(expected = SQLiteException.class) @@ -701,6 +731,7 @@ public void shouldUseInMemoryDatabaseWhenCallingCreate() { SQLiteDatabase db = SQLiteDatabase.create(null); assertThat(db.isOpen()).isTrue(); assertThat(db.getPath()).isEqualTo(":memory:"); + db.close(); } @Test @@ -732,12 +763,14 @@ public void testTwoConcurrentDbConnections() { assertThat(c.getCount()).isEqualTo(1); assertThat(c.moveToNext()).isTrue(); assertThat(c.getString(c.getColumnIndex("data"))).isEqualTo("d1"); + c.close(); c = db2.rawQuery("select * from bar", null); assertThat(c).isNotNull(); assertThat(c.getCount()).isEqualTo(1); assertThat(c.moveToNext()).isTrue(); assertThat(c.getString(c.getColumnIndex("data"))).isEqualTo("d2"); + c.close(); } @Test(expected = SQLiteException.class) @@ -802,6 +835,7 @@ public void testDataInMemoryDatabaseIsPersistentAfterClose() { assertThat(c.getCount()).isEqualTo(1); assertThat(c.moveToNext()).isTrue(); assertThat(c.getString(c.getColumnIndex("data"))).isEqualTo("d1"); + c.close(); } @Test @@ -822,6 +856,7 @@ public void testRawQueryWithFactoryAndCancellationSignal() { } catch (OperationCanceledException e) { // expected } + cursor.close(); } @Test @@ -848,8 +883,7 @@ public void shouldBeAbleToBeUsedFromDifferentThread() { new Thread() { @Override public void run() { - try { - executeQuery("select * from table_name"); + try (Cursor c = executeQuery("select * from table_name")) { } catch (Throwable e) { e.printStackTrace(); error[0] = e; @@ -915,6 +949,7 @@ private void assertEmptyDatabase() { assertThat(cursor.moveToFirst()).isFalse(); assertThat(cursor.isClosed()).isFalse(); assertThat(cursor.getCount()).isEqualTo(0); + cursor.close(); } private void assertNonEmptyDatabase() { @@ -922,6 +957,7 @@ private void assertNonEmptyDatabase() { database.query("table_name", new String[] {"id", "name"}, null, null, null, null, null); assertThat(cursor.moveToFirst()).isTrue(); assertThat(cursor.getCount()).isNotEqualTo(0); + cursor.close(); } @Test @@ -986,6 +1022,7 @@ public void shouldCorrectlyReturnNullValues() { assertThat(nullValuesCursor.getString(i)).isNull(); } assertThat(nullValuesCursor.getBlob(3)).isNull(); + nullValuesCursor.close(); } @Test diff --git a/robolectric/src/test/java/org/robolectric/shadows/SQLiteOpenHelperTest.java b/robolectric/src/test/java/org/robolectric/shadows/SQLiteOpenHelperTest.java index e809827a56a..574a7a2803e 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/SQLiteOpenHelperTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/SQLiteOpenHelperTest.java @@ -4,6 +4,7 @@ import android.content.ContentValues; import android.content.Context; +import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; @@ -35,6 +36,7 @@ public void testConstructorWithNullPathShouldCreateInMemoryDatabase() { SQLiteDatabase database = helper.getReadableDatabase(); assertDatabaseOpened(database, helper); assertInitialDB(database, helper); + helper.close(); } @Test @@ -106,6 +108,8 @@ public void testGetPath() { String expectedPath2 = ApplicationProvider.getApplicationContext().getDatabasePath(path2).getAbsolutePath(); assertThat(helper2.getReadableDatabase().getPath()).isEqualTo(expectedPath2); + helper1.close(); + helper2.close(); } @Test @@ -154,8 +158,9 @@ private void insertData(SQLiteDatabase db, String table, int[] values) { } private void verifyData(SQLiteDatabase db, String table, int expectedVals) { - assertThat(db.query(table, null, null, null, - null, null, null).getCount()).isEqualTo(expectedVals); + try (Cursor cursor = db.query(table, null, null, null, null, null, null)) { + assertThat(cursor.getCount()).isEqualTo(expectedVals); + } } @Test @@ -171,6 +176,7 @@ public void testMultipleDbsPreserveData() { insertData(db2, TABLE_NAME2, new int[]{4, 5, 6}); verifyData(db1, TABLE_NAME1, 2); verifyData(db2, TABLE_NAME2, 3); + helper2.close(); } @Test @@ -191,6 +197,7 @@ public void testCloseOneDbKeepsDataForOther() { db1 = helper.getWritableDatabase(); verifyData(db1, TABLE_NAME1, 2); verifyData(db2, TABLE_NAME2, 3); + helper2.close(); } @Test diff --git a/robolectric/src/test/java/org/robolectric/shadows/SQLiteQueryBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/SQLiteQueryBuilderTest.java index fbc14077cdb..fe683b30985 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/SQLiteQueryBuilderTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/SQLiteQueryBuilderTest.java @@ -66,20 +66,32 @@ public void tearDown() { public void shouldBeAbleToMakeQueries() { Cursor cursor = builder.query(database, new String[] {"rowid"}, null, null, null, null, null); assertThat(cursor.getCount()).isEqualTo(2); + cursor.close(); } @Test public void shouldBeAbleToMakeQueriesWithSelection() { - Cursor cursor = builder.query(database, new String[] {"rowid"}, COL_VALUE + "=?", new String[] {"record1"}, null, null, null); + Cursor cursor = + builder.query( + database, + new String[] {"rowid"}, + COL_VALUE + "=?", + new String[] {"record1"}, + null, + null, + null); assertThat(cursor.getCount()).isEqualTo(1); assertThat(cursor.moveToNext()).isTrue(); assertThat(cursor.getLong(0)).isEqualTo(firstRecordId); + cursor.close(); } @Test public void shouldBeAbleToMakeQueriesWithGrouping() { - Cursor cursor = builder.query(database, new String[] {"rowid"}, null, null, COL_GROUP, null, null); + Cursor cursor = + builder.query(database, new String[] {"rowid"}, null, null, COL_GROUP, null, null); assertThat(cursor.getCount()).isEqualTo(1); + cursor.close(); } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/SQLiteStatementTest.java b/robolectric/src/test/java/org/robolectric/shadows/SQLiteStatementTest.java index eaf0dc099d0..987829b477c 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/SQLiteStatementTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/SQLiteStatementTest.java @@ -117,6 +117,7 @@ public void testExecuteUpdateDelete() { Cursor dataCursor = database.rawQuery("SELECT `name` FROM `routine`", null); assertThat(dataCursor.moveToNext()).isTrue(); assertThat(dataCursor.getString(0)).isEqualTo("Head Press"); + dataCursor.close(); } @Test diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAbstractCursorTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAbstractCursorTest.java index ef0cf35bdc7..100f840c5d6 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAbstractCursorTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAbstractCursorTest.java @@ -8,6 +8,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.ArrayList; import java.util.List; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -23,6 +24,11 @@ public void setUp() throws Exception { cursor = new TestCursor(); } + @After + public void tearDown() { + cursor.close(); + } + @Test public void testMoveToFirst() { cursor.theTable.add("Foobar"); @@ -269,4 +275,4 @@ public boolean isNull(int columnIndex) { throw new UnsupportedOperationException(); } } -} \ No newline at end of file +} diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java index 42cf9c824fe..35572098c29 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java @@ -43,7 +43,7 @@ import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.database.Cursor; -import android.database.sqlite.SQLiteCursor; +import android.database.MatrixCursor; import android.media.AudioManager; import android.net.Uri; import android.os.Bundle; @@ -72,7 +72,6 @@ import org.robolectric.annotation.Config; import org.robolectric.annotation.LooperMode; import org.robolectric.annotation.LooperMode.Mode; -import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowActivity.IntentSenderRequest; import org.robolectric.util.TestRunnable; @@ -756,7 +755,7 @@ public void startAndStopManagingCursorTracksCursors() throws Exception { assertThat(shadowOf(activity).getManagedCursors()).isNotNull(); assertThat(shadowOf(activity).getManagedCursors()).isEmpty(); - Cursor c = Shadow.newInstanceOf(SQLiteCursor.class); + Cursor c = new MatrixCursor(new String[] {"a"}); activity.startManagingCursor(c); assertThat(shadowOf(activity).getManagedCursors()).isNotNull(); @@ -767,6 +766,7 @@ public void startAndStopManagingCursorTracksCursors() throws Exception { assertThat(shadowOf(activity).getManagedCursors()).isNotNull(); assertThat(shadowOf(activity).getManagedCursors()).isEmpty(); + c.close(); } @Test diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapRegionDecoderTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapRegionDecoderTest.java index f1e645ff734..172cf5eac19 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapRegionDecoderTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapRegionDecoderTest.java @@ -2,6 +2,7 @@ import static com.google.common.truth.Truth.assertThat; +import android.content.res.AssetFileDescriptor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; @@ -11,7 +12,6 @@ import com.google.common.io.ByteStreams; import java.awt.image.BufferedImage; import java.io.File; -import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import javax.imageio.ImageIO; @@ -32,10 +32,12 @@ public class ShadowBitmapRegionDecoderTest { public void testNewInstance() throws Exception { assertThat(BitmapRegionDecoder.newInstance(ByteStreams.toByteArray(getImageInputStream()), 0, 0, false)) .isNotNull(); - assertThat(BitmapRegionDecoder.newInstance(getImageFd(), false)) - .isNotNull(); - assertThat(BitmapRegionDecoder.newInstance(getImageInputStream(), false)) - .isNotNull(); + try (AssetFileDescriptor afd = getImageFd()) { + assertThat(BitmapRegionDecoder.newInstance(afd.getFileDescriptor(), false)).isNotNull(); + } + try (InputStream inputStream = getImageInputStream()) { + assertThat(BitmapRegionDecoder.newInstance(inputStream, false)).isNotNull(); + } assertThat(BitmapRegionDecoder.newInstance(getGeneratedImageFile(), false)) .isNotNull(); } @@ -59,17 +61,18 @@ public void testDecodeRegionReturnsExpectedSize() throws IOException { @Test public void testDecodeRegionReturnsExpectedConfig() throws IOException { - BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(getImageInputStream(), false); - - BitmapFactory.Options options = new BitmapFactory.Options(); - assertThat(bitmapRegionDecoder.decodeRegion(new Rect(0, 0, 1, 1), options).getConfig()) - .isEqualTo(Bitmap.Config.ARGB_8888); - options.inPreferredConfig = null; - assertThat(bitmapRegionDecoder.decodeRegion(new Rect(0, 0, 1, 1), options).getConfig()) - .isEqualTo(Bitmap.Config.ARGB_8888); - options.inPreferredConfig = Bitmap.Config.RGB_565; - assertThat(bitmapRegionDecoder.decodeRegion(new Rect(0, 0, 1, 1), options).getConfig()) - .isEqualTo(Bitmap.Config.RGB_565); + try (InputStream inputStream = getImageInputStream()) { + BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false); + BitmapFactory.Options options = new BitmapFactory.Options(); + assertThat(bitmapRegionDecoder.decodeRegion(new Rect(0, 0, 1, 1), options).getConfig()) + .isEqualTo(Bitmap.Config.ARGB_8888); + options.inPreferredConfig = null; + assertThat(bitmapRegionDecoder.decodeRegion(new Rect(0, 0, 1, 1), options).getConfig()) + .isEqualTo(Bitmap.Config.ARGB_8888); + options.inPreferredConfig = Bitmap.Config.RGB_565; + assertThat(bitmapRegionDecoder.decodeRegion(new Rect(0, 0, 1, 1), options).getConfig()) + .isEqualTo(Bitmap.Config.RGB_565); + } } private static InputStream getImageInputStream() { @@ -78,12 +81,11 @@ private static InputStream getImageInputStream() { .openRawResource(R.drawable.robolectric); } - private static FileDescriptor getImageFd() throws Exception { + private static AssetFileDescriptor getImageFd() throws Exception { return ApplicationProvider.getApplicationContext() .getResources() .getAssets() - .openFd("robolectric.png") - .getFileDescriptor(); + .openFd("robolectric.png"); } private String getGeneratedImageFile() throws Exception { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBugreportManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBugreportManagerTest.java index e8235b5f7ed..b5bddd4376b 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBugreportManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBugreportManagerTest.java @@ -20,6 +20,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,12 +36,20 @@ public final class ShadowBugreportManagerTest { private ShadowBugreportManager shadowBugreportManager; private final Context context = ApplicationProvider.getApplicationContext(); + private final List openFds = new ArrayList<>(); @Before public void setUp() { shadowBugreportManager = Shadow.extract(context.getSystemService(Context.BUGREPORT_SERVICE)); } + @After + public void tearDown() throws Exception { + for (ParcelFileDescriptor pfd : openFds) { + pfd.close(); + } + } + @Test public void requestBugreport() { shadowBugreportManager.requestBugreport( @@ -281,7 +292,10 @@ private ParcelFileDescriptor createWriteFile(String fileName) throws IOException f.delete(); } f.createNewFile(); - return ParcelFileDescriptor.open( - f, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND); + ParcelFileDescriptor pfd = + ParcelFileDescriptor.open( + f, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND); + openFds.add(pfd); + return pfd; } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java index 420ab133c25..896e58a9d53 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java @@ -912,12 +912,13 @@ public void getProvider_shouldNotReturnAnyProviderWhenManifestIsNull() { public void openTypedAssetFileDescriptor_shouldOpenDescriptor() throws IOException, RemoteException { Robolectric.setupContentProvider(MyContentProvider.class, AUTHORITY); - AssetFileDescriptor afd = + try (AssetFileDescriptor afd = contentResolver.openTypedAssetFileDescriptor( - Uri.parse("content://" + AUTHORITY + "/whatever"), "*/*", null); + Uri.parse("content://" + AUTHORITY + "/whatever"), "*/*", null)) { FileDescriptor descriptor = afd.getFileDescriptor(); assertThat(descriptor).isNotNull(); + } } @Test diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowCursorAdapterTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowCursorAdapterTest.java index d31912f26af..88c5987b638 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowCursorAdapterTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowCursorAdapterTest.java @@ -10,6 +10,7 @@ import android.widget.CursorAdapter; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,6 +44,12 @@ public void setUp() throws Exception { adapter = new TestAdapter(curs); } + @After + public void tearDown() { + database.close(); + curs.close(); + } + @Test public void testChangeCursor() { assertThat(adapter.getCursor()).isNotNull(); @@ -81,9 +88,11 @@ public void testGetItemId() { } @Test public void shouldNotErrorOnCursorChangeWhenNoFlagsAreSet() throws Exception { - adapter = new TestAdapterWithFlags(curs, 0); - adapter.changeCursor(database.rawQuery("SELECT * FROM table_name;", null)); - assertThat(adapter.getCursor()).isNotSameInstanceAs(curs); + try (Cursor newCursor = database.rawQuery("SELECT * FROM table_name;", null)) { + adapter = new TestAdapterWithFlags(curs, 0); + adapter.changeCursor(newCursor); + assertThat(adapter.getCursor()).isNotSameInstanceAs(curs); + } } private static class TestAdapter extends CursorAdapter { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowCursorWindowTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowCursorWindowTest.java index bf0bfd37116..f21dcdf0d0c 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowCursorWindowTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowCursorWindowTest.java @@ -17,6 +17,7 @@ public class ShadowCursorWindowTest { public void shouldCreateWindowWithName() { CursorWindow window = new CursorWindow("name"); assertThat(window.getName()).isEqualTo("name"); + window.close(); } @Test @@ -45,6 +46,8 @@ public void shouldFillWindowWithCursor() { assertThat(window.getBlob(1, 3)).isEqualTo(null); assertThat(window.getBlob(2, 3)).isEqualTo(new byte[]{}); + testCursor.close(); + window.close(); } /** Real Android will crash in native code if putBlob is called with a null value. */ @@ -53,6 +56,7 @@ public void putBlobNullValueThrowsNPE() { CursorWindow cursorWindow = new CursorWindow("test"); cursorWindow.allocRow(); assertThrows(NullPointerException.class, () -> cursorWindow.putBlob(null, 0, 0)); + cursorWindow.close(); } /** Real Android will crash in native code if putString is called with a null value. */ @@ -61,5 +65,6 @@ public void putStringNullValueThrowsNPE() { CursorWindow cursorWindow = new CursorWindow("test"); cursorWindow.allocRow(); assertThrows(NullPointerException.class, () -> cursorWindow.putString(null, 0, 0)); + cursorWindow.close(); } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowHardwareBufferTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowHardwareBufferTest.java index fcbb21b1688..35d16b69252 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowHardwareBufferTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowHardwareBufferTest.java @@ -175,8 +175,10 @@ public void createWithBlobFormatInvalidHeightThrows() { public void createWithOFormatsAndFlagsSucceedsOnOAndLater() { for (int format : VALID_FORMATS_O) { int height = format == HardwareBuffer.BLOB ? 1 : VALID_HEIGHT; - assertNotNull( - HardwareBuffer.create(VALID_WIDTH, height, format, VALID_LAYERS, VALID_USAGE_FLAGS_O)); + try (HardwareBuffer buffer = + HardwareBuffer.create(VALID_WIDTH, height, format, VALID_LAYERS, VALID_USAGE_FLAGS_O)) { + assertNotNull(buffer); + } } } @@ -184,22 +186,26 @@ public void createWithOFormatsAndFlagsSucceedsOnOAndLater() { @Config(minSdk = P) public void createWithPFormatsAndFlagsSucceedsOnPAndLater() { for (int format : VALID_FORMATS_P) { - assertNotNull( + try (HardwareBuffer buffer = HardwareBuffer.create( - VALID_WIDTH, VALID_HEIGHT, format, VALID_LAYERS, VALID_USAGE_FLAGS_P)); + VALID_WIDTH, VALID_HEIGHT, format, VALID_LAYERS, VALID_USAGE_FLAGS_P)) { + assertNotNull(buffer); + } } } @Test @Config(minSdk = P) public void createWithPFlagsSucceedsOnPAndLater() { - assertNotNull( + try (HardwareBuffer buffer = HardwareBuffer.create( VALID_WIDTH, VALID_HEIGHT, HardwareBuffer.RGBA_8888, VALID_LAYERS, - VALID_USAGE_FLAGS_P)); + VALID_USAGE_FLAGS_P)) { + assertNotNull(buffer); + } } @Test @@ -214,6 +220,7 @@ public void gettersOnHardwareBufferAreCorrect() { assertEquals(HardwareBuffer.RGBA_8888, buffer.getFormat()); assertEquals(VALID_LAYERS, buffer.getLayers()); assertEquals(VALID_USAGE_FLAGS_O, buffer.getUsage()); + buffer.close(); } @Test @@ -267,11 +274,14 @@ public void gettersOnParceledBufferAreCorrect() { final Parcel parcel = Parcel.obtain(); buffer.writeToParcel(parcel, 0); parcel.setDataPosition(0); + HardwareBuffer otherBuffer = HardwareBuffer.CREATOR.createFromParcel(parcel); assertEquals(VALID_WIDTH, otherBuffer.getWidth()); assertEquals(VALID_HEIGHT, otherBuffer.getHeight()); assertEquals(HardwareBuffer.RGBA_8888, otherBuffer.getFormat()); assertEquals(VALID_LAYERS, otherBuffer.getLayers()); assertEquals(VALID_USAGE_FLAGS_O, otherBuffer.getUsage()); + buffer.close(); + otherBuffer.close(); } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMatrixCursorTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMatrixCursorTest.java index 4370896bd8e..019a9e64cf4 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMatrixCursorTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMatrixCursorTest.java @@ -2,12 +2,14 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import android.database.CursorIndexOutOfBoundsException; import android.database.MatrixCursor; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.Arrays; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -24,6 +26,11 @@ public void setUp() throws Exception { singleColumnSingleNullValueMatrixCursor.moveToFirst(); } + @After + public void tearDown() { + singleColumnSingleNullValueMatrixCursor.close(); + } + @Test public void shouldAddObjectArraysAsRows() { MatrixCursor cursor = new MatrixCursor(new String[]{"a", "b", "c"}); @@ -44,6 +51,7 @@ public void shouldAddObjectArraysAsRows() { assertTrue(cursor.isNull(2)); assertFalse(cursor.moveToNext()); + cursor.close(); } @Test @@ -66,6 +74,7 @@ public void shouldAddIterablesAsRows() { assertTrue(cursor.isNull(2)); assertFalse(cursor.moveToNext()); + cursor.close(); } @Test @@ -82,6 +91,7 @@ public void shouldDefineColumnNames() { assertThat(cursor.getColumnIndex("b")).isEqualTo(1); assertThat(cursor.getColumnIndex("z")).isEqualTo(-1); + cursor.close(); } @Test @@ -116,34 +126,39 @@ public void shouldAllowTypeFlexibility() { assertThat(cursor.getDouble(1)).isEqualTo(3.3); assertThat(cursor.getString(2)).isEqualTo("a"); + cursor.close(); } - @Test(expected = IllegalArgumentException.class) + @Test public void shouldDefineGetColumnNameOrThrow() { MatrixCursor cursor = new MatrixCursor(new String[]{"a", "b", "c"}); - cursor.getColumnIndexOrThrow("z"); + assertThrows(IllegalArgumentException.class, () -> cursor.getColumnIndexOrThrow("z")); + cursor.close(); } - @Test(expected = CursorIndexOutOfBoundsException.class) + @Test public void shouldThrowIndexOutOfBoundsExceptionWithoutData() { MatrixCursor cursor = new MatrixCursor(new String[]{"a", "b", "c"}); - cursor.getString(0); + assertThrows(CursorIndexOutOfBoundsException.class, () -> cursor.getString(0)); + cursor.close(); } - @Test(expected = CursorIndexOutOfBoundsException.class) + @Test public void shouldThrowIndexOutOfBoundsExceptionForInvalidColumn() { MatrixCursor cursor = new MatrixCursor(new String[]{"a", "b", "c"}); cursor.addRow(new Object[]{"foo", 10L, 0.1f}); - cursor.getString(3); + assertThrows(CursorIndexOutOfBoundsException.class, () -> cursor.getString(3)); + cursor.close(); } - @Test(expected = CursorIndexOutOfBoundsException.class) + @Test public void shouldThrowIndexOutOfBoundsExceptionForInvalidColumnLastRow() { MatrixCursor cursor = new MatrixCursor(new String[]{"a", "b", "c"}); cursor.addRow(new Object[]{"foo", 10L, 0.1f}); cursor.moveToFirst(); cursor.moveToNext(); - cursor.getString(0); + assertThrows(CursorIndexOutOfBoundsException.class, () -> cursor.getString(0)); + cursor.close(); } @Test diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaMuxerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaMuxerTest.java index 0279ba7d62c..6a8217340b4 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaMuxerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaMuxerTest.java @@ -97,5 +97,6 @@ private void basicMuxingFlow(int bufInfoOffset, int bufOffset, int inputSize) th assertThat(outputBytes) .isEqualTo(Arrays.copyOfRange(inputBytes, bufInfoOffset, inputBytes.length)); new File(tempFilePath).deleteOnExit(); + muxer.release(); } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaPlayerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaPlayerTest.java index 4d9873d8500..6553049c7ca 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaPlayerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaPlayerTest.java @@ -240,11 +240,12 @@ public long getSize() { @Test public void testSetDataSourceAssetFileDescriptorDataSource() throws IOException { Application context = ApplicationProvider.getApplicationContext(); - AssetFileDescriptor fd = context.getResources().openRawResourceFd(R.drawable.an_image); - DataSource ds = toDataSource(fd); - ShadowMediaPlayer.addMediaInfo(ds, info); - mediaPlayer.setDataSource(fd); - assertWithMessage("dataSource").that(shadowMediaPlayer.getDataSource()).isEqualTo(ds); + try (AssetFileDescriptor fd = context.getResources().openRawResourceFd(R.drawable.an_image)) { + DataSource ds = toDataSource(fd); + ShadowMediaPlayer.addMediaInfo(ds, info); + mediaPlayer.setDataSource(fd); + assertWithMessage("dataSource").that(shadowMediaPlayer.getDataSource()).isEqualTo(ds); + } } @Test diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMergeCursorTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMergeCursorTest.java index e0036b41355..f8e3c50cf1f 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMergeCursorTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMergeCursorTest.java @@ -1,12 +1,15 @@ package org.robolectric.shadows; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import android.database.Cursor; import android.database.MergeCursor; import android.database.sqlite.SQLiteCursor; import android.database.sqlite.SQLiteDatabase; import androidx.test.ext.junit.runners.AndroidJUnit4; +import dalvik.system.CloseGuard; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -52,6 +55,16 @@ public void setUp() throws Exception { "SELECT * FROM table_2;"); } + @After + public void tearDown() throws Exception { + database.close(); + if (cursor != null) { + cursor.close(); + } + dbCursor1.close(); + dbCursor2.close(); + } + private SQLiteCursor setupTable(final String createSql, final String[] insertions, final String selectSql) { database.execSQL(createSql); @@ -65,9 +78,16 @@ private SQLiteCursor setupTable(final String createSql, final String[] insertion return (SQLiteCursor) cursor; } - @Test(expected = NullPointerException.class) + @Test public void shouldThrowIfConstructorArgumentIsNull() { - new MergeCursor(null); + CloseGuard.Reporter originalReporter = CloseGuard.getReporter(); + try { + // squelch spurious CloseGuard error + CloseGuard.setReporter((s, throwable) -> {}); + assertThrows(NullPointerException.class, () -> new MergeCursor(null)); + } finally { + CloseGuard.setReporter(originalReporter); + } } @Test @@ -77,6 +97,7 @@ public void testEmptyCursors() { assertThat(cursor.getCount()).isEqualTo(0); assertThat(cursor.moveToFirst()).isFalse(); assertThat(cursor.getColumnNames()).isNotNull(); + cursor.close(); // cursor list with partially null contents Cursor[] cursors = new Cursor[2]; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowParcelFileDescriptorTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowParcelFileDescriptorTest.java index dacb95bc7cb..be3a35cf280 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowParcelFileDescriptorTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowParcelFileDescriptorTest.java @@ -11,6 +11,7 @@ import java.io.FileOutputStream; import java.io.IOException; import org.hamcrest.Matchers; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -22,6 +23,7 @@ public class ShadowParcelFileDescriptorTest { private File file; private File readOnlyFile; + private ParcelFileDescriptor pfd; @Before public void setUp() throws Exception { @@ -36,30 +38,32 @@ public void setUp() throws Exception { assertThat(readOnlyFile.setReadOnly()).isTrue(); } + @After + public void tearDown() throws Exception { + if (pfd != null) { + pfd.close(); + } + } + @Test public void testOpens() throws Exception { - ParcelFileDescriptor pfd = - ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); + pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); assertThat(pfd).isNotNull(); assertThat(pfd.getFileDescriptor().valid()).isTrue(); - pfd.close(); } @Test public void testOpens_canReadReadOnlyFile() throws Exception { - ParcelFileDescriptor pfd = - ParcelFileDescriptor.open(readOnlyFile, ParcelFileDescriptor.MODE_READ_ONLY); + pfd = ParcelFileDescriptor.open(readOnlyFile, ParcelFileDescriptor.MODE_READ_ONLY); assertThat(pfd).isNotNull(); assertThat(pfd.getFileDescriptor().valid()).isTrue(); FileInputStream is = new FileInputStream(pfd.getFileDescriptor()); assertThat(is.read()).isEqualTo(READ_ONLY_FILE_CONTENTS); - pfd.close(); } @Test public void testOpens_canWriteWritableFile() throws Exception { - ParcelFileDescriptor pfd = - ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); + pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); assertThat(pfd).isNotNull(); assertThat(pfd.getFileDescriptor().valid()).isTrue(); FileOutputStream os = new FileOutputStream(pfd.getFileDescriptor()); @@ -69,18 +73,15 @@ public void testOpens_canWriteWritableFile() throws Exception { @Test public void testStatSize_emptyFile() throws Exception { - ParcelFileDescriptor pfd = - ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); + pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); assertThat(pfd).isNotNull(); assertThat(pfd.getFileDescriptor().valid()).isTrue(); assertThat(pfd.getStatSize()).isEqualTo(0); - pfd.close(); } @Test public void testStatSize_writtenFile() throws Exception { - ParcelFileDescriptor pfd = - ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); + pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); assertThat(pfd).isNotNull(); assertThat(pfd.getFileDescriptor().valid()).isTrue(); FileOutputStream os = new FileOutputStream(pfd.getFileDescriptor()); @@ -91,14 +92,14 @@ public void testStatSize_writtenFile() throws Exception { @Test public void testAppend() throws Exception { - ParcelFileDescriptor pfd = - ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); + pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); assertThat(pfd).isNotNull(); assertThat(pfd.getFileDescriptor().valid()).isTrue(); FileOutputStream os = new FileOutputStream(pfd.getFileDescriptor()); os.write(5); assertThat(pfd.getStatSize()).isEqualTo(1); // One byte. os.close(); + pfd.close(); pfd = ParcelFileDescriptor.open( @@ -113,8 +114,7 @@ public void testAppend() throws Exception { @Test public void testTruncate() throws Exception { - ParcelFileDescriptor pfd = - ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); + pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); assertThat(pfd).isNotNull(); assertThat(pfd.getFileDescriptor().valid()).isTrue(); FileOutputStream os = new FileOutputStream(pfd.getFileDescriptor()); @@ -131,6 +131,7 @@ public void testTruncate() throws Exception { assertThat(in.available()).isEqualTo(0); } + pfd.close(); pfd = ParcelFileDescriptor.open( file, ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_TRUNCATE); @@ -149,8 +150,7 @@ public void testTruncate() throws Exception { @Test public void testWriteTwiceNoTruncate() throws Exception { - ParcelFileDescriptor pfd = - ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); + pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); assertThat(pfd).isNotNull(); assertThat(pfd.getFileDescriptor().valid()).isTrue(); FileOutputStream os = new FileOutputStream(pfd.getFileDescriptor()); @@ -166,6 +166,7 @@ public void testWriteTwiceNoTruncate() throws Exception { assertThat(buffer).isEqualTo(new byte[] {1, 2, 3}); assertThat(in.available()).isEqualTo(0); } + pfd.close(); pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); assertThat(pfd).isNotNull(); @@ -185,7 +186,7 @@ public void testWriteTwiceNoTruncate() throws Exception { @Test public void testCloses() throws Exception { - ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, -1); + pfd = ParcelFileDescriptor.open(file, -1); pfd.close(); assertThat(pfd.getFileDescriptor().valid()).isFalse(); assertThat(pfd.getFd()).isEqualTo(-1); @@ -193,14 +194,14 @@ public void testCloses() throws Exception { @Test public void testCloses_getStatSize_returnsInvalidLength() throws Exception { - ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, -1); + pfd = ParcelFileDescriptor.open(file, -1); pfd.close(); assertThat(pfd.getStatSize()).isEqualTo(-1); } @Test public void testAutoCloseInputStream() throws Exception { - ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, -1); + pfd = ParcelFileDescriptor.open(file, -1); ParcelFileDescriptor.AutoCloseInputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd); is.close(); @@ -247,8 +248,7 @@ public void testGetFd_canRead() throws IOException { assumeThat("Windows is an affront to decency.", File.separator, Matchers.equalTo("/")); - ParcelFileDescriptor pfd = - ParcelFileDescriptor.open(readOnlyFile, ParcelFileDescriptor.MODE_READ_ONLY); + pfd = ParcelFileDescriptor.open(readOnlyFile, ParcelFileDescriptor.MODE_READ_ONLY); int fd = pfd.getFd(); assertThat(fd).isGreaterThan(0); FileInputStream is = new FileInputStream(new File("/proc/self/fd/" + fd)); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowRenderNodeTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowRenderNodeTest.java new file mode 100644 index 00000000000..0e0db151cc6 --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowRenderNodeTest.java @@ -0,0 +1,1078 @@ +package org.robolectric.shadows; + +import static android.os.Build.VERSION_CODES.LOLLIPOP; +import static android.os.Build.VERSION_CODES.Q; +import static com.google.common.truth.Truth.assertThat; +import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; +import static org.robolectric.util.ReflectionHelpers.callInstanceMethod; +import static org.robolectric.util.ReflectionHelpers.callStaticMethod; +import static org.robolectric.util.ReflectionHelpers.loadClass; + +import android.annotation.TargetApi; +import android.graphics.Matrix; +import android.graphics.RenderNode; +import android.os.Build; +import android.view.View; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** + * Android-Q only test for {@code RenderNode}'s shadow for both pre-Q & Q (where the latter's {@code + * RenderNode} was moved to a public API to open access to it. + */ +@RunWith(AndroidJUnit4.class) +@Config(minSdk = LOLLIPOP) +public final class ShadowRenderNodeTest { + + @Before + public void setup() { + System.setProperty("robolectric.rendernode.enableMatrix", "true"); + } + + @Test + public void testGetTranslationX_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float translationX = renderNode.getTranslationX(); + + assertThat(translationX).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetTranslationX_set_returnsSetTranslation() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setTranslationX(1.f); + + float translationX = renderNode.getTranslationX(); + + assertThat(translationX).isWithin(1e-3f).of(1.f); + } + + @Test + public void testGetTranslationY_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float translationY = renderNode.getTranslationY(); + + assertThat(translationY).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetTranslationY_set_returnsSetTranslation() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setTranslationY(1.f); + + float translationY = renderNode.getTranslationY(); + + assertThat(translationY).isWithin(1e-3f).of(1.f); + } + + @Test + public void testGetRotationX_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float rotationX = renderNode.getRotationX(); + + assertThat(rotationX).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetRotationX_set_returnsSetRotationX() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setRotationX(45.f); + + float rotationX = renderNode.getRotationX(); + + assertThat(rotationX).isWithin(1e-3f).of(45.f); + } + + @Test + public void testGetRotationY_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float rotationY = renderNode.getRotationY(); + + assertThat(rotationY).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetRotationY_set_returnsSetRotationY() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setRotationY(45.f); + + float rotationY = renderNode.getRotationY(); + + assertThat(rotationY).isWithin(1e-3f).of(45.f); + } + + @Test + public void testGetRotationZ_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float rotationZ = renderNode.getRotationZ(); + + assertThat(rotationZ).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetRotationZ_set_returnsSetRotationZ() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setRotationZ(45.f); + + float rotationZ = renderNode.getRotationZ(); + + assertThat(rotationZ).isWithin(1e-3f).of(45.f); + } + + @Test + public void testGetScaleX_unset_returnsOne() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float scaleX = renderNode.getScaleX(); + + assertThat(scaleX).isWithin(1e-3f).of(1.f); + } + + @Test + public void testGetScaleX_set_returnsSetScale() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setScaleX(2.f); + + float scaleX = renderNode.getScaleX(); + + assertThat(scaleX).isWithin(1e-3f).of(2.f); + } + + @Test + public void testGetScaleY_unset_returnsOne() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float scaleY = renderNode.getScaleY(); + + assertThat(scaleY).isWithin(1e-3f).of(1.f); + } + + @Test + public void testGetScaleY_set_returnsSetScale() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setScaleY(2.f); + + float scaleY = renderNode.getScaleY(); + + assertThat(scaleY).isWithin(1e-3f).of(2.f); + } + + @Test + public void testGetPivotX_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float pivotX = renderNode.getPivotX(); + + assertThat(pivotX).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetPivotX_set_returnsSetPivot() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setPivotX(1.f); + + float pivotX = renderNode.getPivotX(); + + assertThat(pivotX).isWithin(1e-3f).of(1.f); + } + + @Test + public void testGetPivotY_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float pivotY = renderNode.getPivotY(); + + assertThat(pivotY).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetPivotY_set_returnsSetPivot() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setPivotY(1.f); + + float pivotY = renderNode.getPivotY(); + + assertThat(pivotY).isWithin(1e-3f).of(1.f); + } + + @Test + public void testIsPivotExplicitlySet_defaultNode_returnsFalse() { + RenderNodeAccess renderNode = createRenderNode("test"); + + boolean isExplicitlySet = renderNode.isPivotExplicitlySet(); + + assertThat(isExplicitlySet).isFalse(); + } + + @Test + public void testIsPivotExplicitlySet_setPivotX_returnsTrue() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setPivotX(1.f); + + boolean isExplicitlySet = renderNode.isPivotExplicitlySet(); + + assertThat(isExplicitlySet).isTrue(); + } + + @Test + public void testIsPivotExplicitlySet_setPivotY_returnsTrue() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setPivotY(1.f); + + boolean isExplicitlySet = renderNode.isPivotExplicitlySet(); + + assertThat(isExplicitlySet).isTrue(); + } + + @Test + public void testIsPivotExplicitlySet_setPivotXY_toDefaultValues_returnsFalse() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setPivotX(0.f); + renderNode.setPivotY(0.f); + + boolean isExplicitlySet = renderNode.isPivotExplicitlySet(); + + // Setting the pivot to the center should result in the pivot not being explicitly set. + assertThat(isExplicitlySet).isTrue(); + } + + @Test + public void testHasIdentityMatrix_defaultNode_returnsTrue() { + RenderNodeAccess renderNode = createRenderNode("test"); + + boolean hasIdentityMatrix = renderNode.hasIdentityMatrix(); + + assertThat(hasIdentityMatrix).isTrue(); + } + + @Test + public void testHasIdentityMatrix_updatedTranslationX_returnsFalse() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setTranslationX(1.f); + + boolean hasIdentityMatrix = renderNode.hasIdentityMatrix(); + + assertThat(hasIdentityMatrix).isFalse(); + } + + @Test + public void testHasIdentityMatrix_updatedRotationX_returnsTrue() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setRotationX(90.f); + + boolean hasIdentityMatrix = renderNode.hasIdentityMatrix(); + + // Rotations about the x-axis are not factored into the render node's transformation matrix. + assertThat(hasIdentityMatrix).isTrue(); + } + + @Test + public void testGetMatrix_defaultNode_returnsIdentityMatrix() { + RenderNodeAccess renderNode = createRenderNode("test"); + + Matrix matrix = new Matrix(); + renderNode.getMatrix(matrix); + + assertThat(matrix.isIdentity()).isTrue(); + } + + @Test + public void testGetMatrix_updatedTranslationX_returnsNonIdentityMatrix() { + RenderNodeAccess renderNode = createRenderNode("test"); + + renderNode.setTranslationX(1.f); + + Matrix matrix = new Matrix(); + renderNode.getMatrix(matrix); + assertThat(matrix.isIdentity()).isFalse(); + } + + @Test + public void testGetMatrix_updatedTranslationX_thenY_returnsDifferentMatrix() { + RenderNodeAccess renderNode = createRenderNode("test"); + + renderNode.setTranslationX(1.f); + Matrix matrix1 = new Matrix(); + renderNode.getMatrix(matrix1); + + renderNode.setTranslationY(1.f); + Matrix matrix2 = new Matrix(); + renderNode.getMatrix(matrix2); + + assertThat(matrix1).isNotEqualTo(matrix2); + } + + @Test + public void testGetMatrix_updatedTranslation_returnsMatrixWithTranslation() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setTranslationX(2.f); + renderNode.setTranslationY(3.f); + + Matrix matrix = new Matrix(); + renderNode.getMatrix(matrix); + + float[] values = new float[9]; + matrix.getValues(values); + assertThat(values[Matrix.MTRANS_X]).isWithin(1e-3f).of(2.f); + assertThat(values[Matrix.MTRANS_Y]).isWithin(1e-3f).of(3.f); + } + + @Test + public void testGetMatrix_updatedRotation_noPivot_mappedPoint_pointRotatesCorrectly() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setRotationZ(90.f); + Matrix matrix = new Matrix(); + renderNode.getMatrix(matrix); + + float[] point = {2.f, 5.f}; + matrix.mapPoints(point); + + // A point rotated counterclockwise 90 degrees will now be across the y-axis and flipped. + assertThat(point[0]).isWithin(1e-3f).of(-5.f); + assertThat(point[1]).isWithin(1e-3f).of(2.f); + } + + @Test + public void testGetMatrix_updatedRotation_withPivot_mappedPoint_pointRotatesCorrectly() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setPivotX(1.f); + renderNode.setPivotY(2.f); + renderNode.setRotationZ(90.f); + Matrix matrix = new Matrix(); + renderNode.getMatrix(matrix); + + float[] point = {2.f, 5.f}; + matrix.mapPoints(point); + + /* + * A point rotated counterclockwise 90 degrees will now be across the y-axis and flipped. + * However, it's further translated due to the rotation above the pivot point (1, 2). See: + * Rotation of v about X by D: Rt(v, X, D) = R(D) * (v - X) + X + * where Rm(D) is the rotation transformation counterclockwise by D degrees. The above + * shifts the pivot point to the origin, rotates about the origin, then shifts back. + * Applied: Rt((2, 5), (1, 2), 90) = R(90) * ((2, 5) - (1, 2)) + (1, 2). + * R(90) * (1, 3) = (-3, 1) -> (-3, 1) + (1, 2) = (-2, 3) + */ + assertThat(point[0]).isWithin(1e-3f).of(-2.f); + assertThat(point[1]).isWithin(1e-3f).of(3.f); + } + + @Test + public void testGetMatrix_updatedScale_noPivot_mappedPoint_pointScalesCorrectly() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setScaleX(1.5f); + renderNode.setScaleY(2.f); + Matrix matrix = new Matrix(); + renderNode.getMatrix(matrix); + + float[] point = {2.f, 5.f}; + matrix.mapPoints(point); + + // (2, 5) * (1.5, 2) = (3, 10) + assertThat(point[0]).isWithin(1e-3f).of(3.f); + assertThat(point[1]).isWithin(1e-3f).of(10.f); + } + + @Test + public void testGetMatrix_updatedScale_withPivot_mappedPoint_pointScalesCorrectly() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setScaleX(1.5f); + renderNode.setScaleY(2.f); + renderNode.setPivotX(1); + renderNode.setPivotY(2); + Matrix matrix = new Matrix(); + renderNode.getMatrix(matrix); + + float[] point = {2.f, 5.f}; + matrix.mapPoints(point); + + // See the rotation about a pivot above for the explanation for performing a linear + // transformation about a point. + // 1. (2, 5) - (1, 2) = (1, 3) + // 2. (1, 3) * (1.5, 2) = (1.5, 6) + // 3. (1.5, 6) + (1, 2) = (2.5, 8) + assertThat(point[0]).isWithin(1e-3f).of(2.5f); + assertThat(point[1]).isWithin(1e-3f).of(8.f); + } + + @Test + public void testGetMatrix_updatedScaleTranslationRotation_withPivot_mappedPoint_pointXformed() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setTranslationX(-6.f); + renderNode.setTranslationY(-3.f); + renderNode.setScaleX(1.5f); + renderNode.setScaleY(2.f); + renderNode.setPivotX(1); + renderNode.setPivotY(2); + renderNode.setRotationZ(90.f); + Matrix matrix = new Matrix(); + renderNode.getMatrix(matrix); + + float[] point = {2.f, 5.f}; + matrix.mapPoints(point); + + // See the pivot tests above for scale & rotation to follow this math. Transformations should be + // scale, then rotation, then translation. Both the scale and rotation share a pivot. + // 1. (2, 5) - (1, 2) = (1, 3) + // 2. (1, 3) * (1.5, 2) = (1.5, 6) + // 3. rotate(1.5, 6, 90) = (-6, 1.5) + // 4. (-6, 1.5) + (1, 2) = (-5, 3.5) + // 5. (-5, 3.5) + (-6, -3) = (-11, 0.5) + assertThat(point[0]).isWithin(1e-3f).of(-11.f); + assertThat(point[1]).isWithin(1e-3f).of(0.5f); + } + + @Test + public void testGetInverseMatrix_defaultNode_returnsIdentityMatrix() { + RenderNodeAccess renderNode = createRenderNode("test"); + + Matrix matrix = new Matrix(); + renderNode.getInverseMatrix(matrix); + + assertThat(matrix.isIdentity()).isTrue(); + } + + @Test + public void testGetInverseMatrix_updatedTranslationX_returnsNonIdentityMatrix() { + RenderNodeAccess renderNode = createRenderNode("test"); + + renderNode.setTranslationX(1.f); + + Matrix matrix = new Matrix(); + renderNode.getInverseMatrix(matrix); + assertThat(matrix.isIdentity()).isFalse(); + } + + @Test + public void testGetInverseMatrix_updatedTranslationX_thenY_returnsDifferentMatrix() { + RenderNodeAccess renderNode = createRenderNode("test"); + + renderNode.setTranslationX(1.f); + Matrix matrix1 = new Matrix(); + renderNode.getInverseMatrix(matrix1); + + renderNode.setTranslationY(1.f); + Matrix matrix2 = new Matrix(); + renderNode.getInverseMatrix(matrix2); + + assertThat(matrix1).isNotEqualTo(matrix2); + } + + @Test + public void testGetInverseMatrix_updatedScaleTranslationRotation_withPivot_mappedPoint_inverts() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setTranslationX(-6.f); + renderNode.setTranslationY(-3.f); + renderNode.setScaleX(1.5f); + renderNode.setScaleY(2.f); + renderNode.setPivotX(1); + renderNode.setPivotY(2); + renderNode.setRotationZ(90.f); + Matrix inverse = new Matrix(); + renderNode.getInverseMatrix(inverse); + + float[] point = {-11f, 0.5f}; + inverse.mapPoints(point); + + // See testGetMatrix_updatedScaleTranslationRotation_withPivot_mappedPoint_pointXformed above + // for why the point (-11, 0.5) produces (2, 5) when mapped via the inverse matrix. + assertThat(point[0]).isWithin(1e-3f).of(2.f); + assertThat(point[1]).isWithin(1e-3f).of(5.f); + } + + @Test + public void testGetMatrix_complexMatrix_multipliedByInverse_producesIdentity() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setTranslationX(-6.f); + renderNode.setTranslationY(-3.f); + renderNode.setScaleX(1.5f); + renderNode.setScaleY(2.f); + renderNode.setPivotX(1); + renderNode.setPivotY(2); + renderNode.setRotationZ(90.f); + + Matrix matrix = new Matrix(); + Matrix inverse = new Matrix(); + Matrix product = new Matrix(); + renderNode.getMatrix(matrix); + renderNode.getInverseMatrix(inverse); + product.postConcat(matrix); + product.postConcat(inverse); + + assertThat(product.isIdentity()).isTrue(); + } + + @Test + public void testGetAlpha_unset_returnsOne() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float alpha = renderNode.getAlpha(); + + assertThat(alpha).isWithin(1e-3f).of(1.f); + } + + @Test + public void testGetAlpha_set_returnsSetAlpha() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setAlpha(0.5f); + + float alpha = renderNode.getAlpha(); + + assertThat(alpha).isWithin(1e-3f).of(0.5f); + } + + @Test + public void testGetCameraDistance_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float cameraDistance = renderNode.getCameraDistance(); + + assertThat(cameraDistance).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetCameraDistance_set_returnsSetCameraDistance() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setCameraDistance(2.3f); + + float cameraDistance = renderNode.getCameraDistance(); + + assertThat(cameraDistance).isWithin(1e-3f).of(2.3f); + } + + @Test + public void testGetClipToOutline_unset_returnsFalse() { + RenderNodeAccess renderNode = createRenderNode("test"); + + boolean clipToOutline = renderNode.getClipToOutline(); + + assertThat(clipToOutline).isFalse(); + } + + @Test + public void testGetClipToOutline_set_returnsTrue() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setClipToOutline(true); + + boolean clipToOutline = renderNode.getClipToOutline(); + + assertThat(clipToOutline).isTrue(); + } + + @Test + public void testGetElevation_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float elevation = renderNode.getElevation(); + + assertThat(elevation).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetElevation_set_returnsSetElevation() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setElevation(2.f); + + float elevation = renderNode.getElevation(); + + assertThat(elevation).isWithin(1e-3f).of(2.f); + } + + @Test + public void testHasOverlappingRendering_unset_returnsFalse() { + RenderNodeAccess renderNode = createRenderNode("test"); + + boolean hasOverlappingRendering = renderNode.hasOverlappingRendering(); + + assertThat(hasOverlappingRendering).isFalse(); + } + + @Test + public void testHasOverlappingRendering_set_returnsTrue() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setHasOverlappingRendering(true); + + boolean hasOverlappingRendering = renderNode.hasOverlappingRendering(); + + assertThat(hasOverlappingRendering).isTrue(); + } + + private static RenderNodeAccess createRenderNode(String name) { + if (Build.VERSION.SDK_INT < Q) { + return new RenderNodeAccessPreQ(name); + } else { + return new RenderNodeAccessPostQ(name); + } + } + + /** + * Provides access to a {@code RenderNode} depending on the version of Android being used. This + * class is needed since multiple versions of {@code RenderNode} exist depending on the SDK + * version. + */ + private interface RenderNodeAccess { + boolean setAlpha(float alpha); + + float getAlpha(); + + boolean setCameraDistance(float cameraDistance); + + float getCameraDistance(); + + boolean setClipToOutline(boolean clipToOutline); + + boolean getClipToOutline(); + + boolean setElevation(float lift); + + float getElevation(); + + boolean setHasOverlappingRendering(boolean overlappingRendering); + + boolean hasOverlappingRendering(); + + boolean setRotationZ(float rotationZ); + + float getRotationZ(); + + boolean setRotationX(float rotationX); + + float getRotationX(); + + boolean setRotationY(float rotationY); + + float getRotationY(); + + boolean setScaleX(float scaleX); + + float getScaleX(); + + boolean setScaleY(float scaleY); + + float getScaleY(); + + boolean setTranslationX(float translationX); + + boolean setTranslationY(float translationY); + + boolean setTranslationZ(float translationZ); + + float getTranslationX(); + + float getTranslationY(); + + float getTranslationZ(); + + boolean isPivotExplicitlySet(); + + boolean setPivotX(float pivotX); + + float getPivotX(); + + boolean setPivotY(float pivotY); + + float getPivotY(); + + boolean hasIdentityMatrix(); + + void getMatrix(Matrix outMatrix); + + void getInverseMatrix(Matrix outMatrix); + } + + /** Provides access to {@link android.view.RenderNode}. */ + private static final class RenderNodeAccessPreQ implements RenderNodeAccess { + private final Class renderNodeClass; + private final Object renderNode; + + private RenderNodeAccessPreQ(String name) { + renderNodeClass = + loadClass( + ApplicationProvider.getApplicationContext().getClass().getClassLoader(), + "android.view.RenderNode"); + renderNode = + callStaticMethod( + renderNodeClass, + "create", + from(String.class, name), + from(View.class, /* val= */ null)); + } + + @Override + public boolean setAlpha(float alpha) { + return callInstanceMethod(renderNodeClass, renderNode, "setAlpha", from(float.class, alpha)); + } + + @Override + public float getAlpha() { + return callInstanceMethod(renderNodeClass, renderNode, "getAlpha"); + } + + @Override + public boolean setCameraDistance(float cameraDistance) { + return callInstanceMethod( + renderNodeClass, renderNode, "setCameraDistance", from(float.class, cameraDistance)); + } + + @Override + public float getCameraDistance() { + return callInstanceMethod(renderNodeClass, renderNode, "getCameraDistance"); + } + + @Override + public boolean setClipToOutline(boolean clipToOutline) { + return callInstanceMethod( + renderNodeClass, renderNode, "setClipToOutline", from(boolean.class, clipToOutline)); + } + + @Override + public boolean getClipToOutline() { + return callInstanceMethod(renderNodeClass, renderNode, "getClipToOutline"); + } + + @Override + public boolean setElevation(float lift) { + return callInstanceMethod( + renderNodeClass, renderNode, "setElevation", from(float.class, lift)); + } + + @Override + public float getElevation() { + return callInstanceMethod(renderNodeClass, renderNode, "getElevation"); + } + + @Override + public boolean setHasOverlappingRendering(boolean overlappingRendering) { + return callInstanceMethod( + renderNodeClass, + renderNode, + "setHasOverlappingRendering", + from(boolean.class, overlappingRendering)); + } + + @Override + public boolean hasOverlappingRendering() { + return callInstanceMethod(renderNodeClass, renderNode, "hasOverlappingRendering"); + } + + @Override + public boolean setRotationZ(float rotationZ) { + return callInstanceMethod( + renderNodeClass, renderNode, "setRotation", from(float.class, rotationZ)); + } + + @Override + public float getRotationZ() { + return callInstanceMethod(renderNodeClass, renderNode, "getRotation"); + } + + @Override + public boolean setRotationX(float rotationX) { + return callInstanceMethod( + renderNodeClass, renderNode, "setRotationX", from(float.class, rotationX)); + } + + @Override + public float getRotationX() { + return callInstanceMethod(renderNodeClass, renderNode, "getRotationX"); + } + + @Override + public boolean setRotationY(float rotationY) { + return callInstanceMethod( + renderNodeClass, renderNode, "setRotationY", from(float.class, rotationY)); + } + + @Override + public float getRotationY() { + return callInstanceMethod(renderNodeClass, renderNode, "getRotationY"); + } + + @Override + public boolean setScaleX(float scaleX) { + return callInstanceMethod( + renderNodeClass, renderNode, "setScaleX", from(float.class, scaleX)); + } + + @Override + public float getScaleX() { + return callInstanceMethod(renderNodeClass, renderNode, "getScaleX"); + } + + @Override + public boolean setScaleY(float scaleY) { + return callInstanceMethod( + renderNodeClass, renderNode, "setScaleY", from(float.class, scaleY)); + } + + @Override + public float getScaleY() { + return callInstanceMethod(renderNodeClass, renderNode, "getScaleY"); + } + + @Override + public boolean setTranslationX(float translationX) { + return callInstanceMethod( + renderNodeClass, renderNode, "setTranslationX", from(float.class, translationX)); + } + + @Override + public boolean setTranslationY(float translationY) { + return callInstanceMethod( + renderNodeClass, renderNode, "setTranslationY", from(float.class, translationY)); + } + + @Override + public boolean setTranslationZ(float translationZ) { + return callInstanceMethod( + renderNodeClass, renderNode, "setTranslationZ", from(float.class, translationZ)); + } + + @Override + public float getTranslationX() { + return callInstanceMethod(renderNodeClass, renderNode, "getTranslationX"); + } + + @Override + public float getTranslationY() { + return callInstanceMethod(renderNodeClass, renderNode, "getTranslationY"); + } + + @Override + public float getTranslationZ() { + return callInstanceMethod(renderNodeClass, renderNode, "getTranslationZ"); + } + + @Override + public boolean isPivotExplicitlySet() { + return callInstanceMethod(renderNodeClass, renderNode, "isPivotExplicitlySet"); + } + + @Override + public boolean setPivotX(float pivotX) { + return callInstanceMethod( + renderNodeClass, renderNode, "setPivotX", from(float.class, pivotX)); + } + + @Override + public float getPivotX() { + return callInstanceMethod(renderNodeClass, renderNode, "getPivotX"); + } + + @Override + public boolean setPivotY(float pivotY) { + return callInstanceMethod( + renderNodeClass, renderNode, "setPivotY", from(float.class, pivotY)); + } + + @Override + public float getPivotY() { + return callInstanceMethod(renderNodeClass, renderNode, "getPivotY"); + } + + @Override + public boolean hasIdentityMatrix() { + return callInstanceMethod(renderNodeClass, renderNode, "hasIdentityMatrix"); + } + + @Override + public void getMatrix(Matrix outMatrix) { + callInstanceMethod(renderNodeClass, renderNode, "getMatrix", from(Matrix.class, outMatrix)); + } + + @Override + public void getInverseMatrix(Matrix outMatrix) { + callInstanceMethod( + renderNodeClass, renderNode, "getInverseMatrix", from(Matrix.class, outMatrix)); + } + } + + /** Provides access to {@link android.graphics.RenderNode}. */ + @TargetApi(Q) + private static final class RenderNodeAccessPostQ implements RenderNodeAccess { + private final RenderNode renderNode; + + private RenderNodeAccessPostQ(String name) { + renderNode = new RenderNode(name); + } + + @Override + public boolean setAlpha(float alpha) { + return renderNode.setAlpha(alpha); + } + + @Override + public float getAlpha() { + return renderNode.getAlpha(); + } + + @Override + public boolean setCameraDistance(float cameraDistance) { + return renderNode.setCameraDistance(cameraDistance); + } + + @Override + public float getCameraDistance() { + return renderNode.getCameraDistance(); + } + + @Override + public boolean setClipToOutline(boolean clipToOutline) { + return renderNode.setClipToOutline(clipToOutline); + } + + @Override + public boolean getClipToOutline() { + return renderNode.getClipToOutline(); + } + + @Override + public boolean setElevation(float lift) { + return renderNode.setElevation(lift); + } + + @Override + public float getElevation() { + return renderNode.getElevation(); + } + + @Override + public boolean setHasOverlappingRendering(boolean overlappingRendering) { + return renderNode.setHasOverlappingRendering(overlappingRendering); + } + + @Override + public boolean hasOverlappingRendering() { + return renderNode.hasOverlappingRendering(); + } + + @Override + public boolean setRotationZ(float rotationZ) { + return renderNode.setRotationZ(rotationZ); + } + + @Override + public float getRotationZ() { + return renderNode.getRotationZ(); + } + + @Override + public boolean setRotationX(float rotationX) { + return renderNode.setRotationX(rotationX); + } + + @Override + public float getRotationX() { + return renderNode.getRotationX(); + } + + @Override + public boolean setRotationY(float rotationY) { + return renderNode.setRotationY(rotationY); + } + + @Override + public float getRotationY() { + return renderNode.getRotationY(); + } + + @Override + public boolean setScaleX(float scaleX) { + return renderNode.setScaleX(scaleX); + } + + @Override + public float getScaleX() { + return renderNode.getScaleX(); + } + + @Override + public boolean setScaleY(float scaleY) { + return renderNode.setScaleY(scaleY); + } + + @Override + public float getScaleY() { + return renderNode.getScaleY(); + } + + @Override + public boolean setTranslationX(float translationX) { + return renderNode.setTranslationX(translationX); + } + + @Override + public boolean setTranslationY(float translationY) { + return renderNode.setTranslationY(translationY); + } + + @Override + public boolean setTranslationZ(float translationZ) { + return renderNode.setTranslationZ(translationZ); + } + + @Override + public float getTranslationX() { + return renderNode.getTranslationX(); + } + + @Override + public float getTranslationY() { + return renderNode.getTranslationY(); + } + + @Override + public float getTranslationZ() { + return renderNode.getTranslationZ(); + } + + @Override + public boolean isPivotExplicitlySet() { + return renderNode.isPivotExplicitlySet(); + } + + @Override + public boolean setPivotX(float pivotX) { + return renderNode.setPivotX(pivotX); + } + + @Override + public float getPivotX() { + return renderNode.getPivotX(); + } + + @Override + public boolean setPivotY(float pivotY) { + return renderNode.setPivotY(pivotY); + } + + @Override + public float getPivotY() { + return renderNode.getPivotY(); + } + + @Override + public boolean hasIdentityMatrix() { + return renderNode.hasIdentityMatrix(); + } + + @Override + public void getMatrix(Matrix outMatrix) { + renderNode.getMatrix(outMatrix); + } + + @Override + public void getInverseMatrix(Matrix outMatrix) { + renderNode.getInverseMatrix(outMatrix); + } + } +} diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java index 7b6e484f864..f3925429771 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java @@ -4,6 +4,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.robolectric.shadows.ShadowAssetManager.useLegacy; +import android.content.res.AssetFileDescriptor; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; @@ -75,11 +76,13 @@ public void openRawResource_shouldLoadDrawableWithQualifiers() { } @Test - public void openRawResourceFd_returnsNull_todo_FIX() { - if (useLegacy()) { - assertThat(resources.openRawResourceFd(R.raw.raw_resource)).isNull(); - } else { - assertThat(resources.openRawResourceFd(R.raw.raw_resource)).isNotNull(); + public void openRawResourceFd_returnsNull_todo_FIX() throws Exception { + try (AssetFileDescriptor afd = resources.openRawResourceFd(R.raw.raw_resource)) { + if (useLegacy()) { + assertThat(afd).isNull(); + } else { + assertThat(afd).isNotNull(); + } } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSimpleCursorAdapterTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSimpleCursorAdapterTest.java index 115d57b865e..c764522c8c6 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSimpleCursorAdapterTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSimpleCursorAdapterTest.java @@ -8,6 +8,7 @@ import android.widget.SimpleCursorAdapter; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -16,10 +17,35 @@ public class ShadowSimpleCursorAdapterTest { private Application context; + private SQLiteDatabase database; + private Cursor cursor; @Before public void setUp() throws Exception { context = ApplicationProvider.getApplicationContext(); + + database = SQLiteDatabase.create(null); + database.execSQL("CREATE TABLE table_name(_id INT PRIMARY KEY, name VARCHAR(255));"); + String[] inserts = { + "INSERT INTO table_name (_id, name) VALUES(1234, 'Chuck');", + "INSERT INTO table_name (_id, name) VALUES(1235, 'Julie');", + "INSERT INTO table_name (_id, name) VALUES(1236, 'Chris');", + "INSERT INTO table_name (_id, name) VALUES(1237, 'Brenda');", + "INSERT INTO table_name (_id, name) VALUES(1238, 'Jane');" + }; + + for (String insert : inserts) { + database.execSQL(insert); + } + + String sql = "SELECT * FROM table_name;"; + cursor = database.rawQuery(sql, null); + } + + @After + public void tearDown() { + database.close(); + cursor.close(); } @Test @@ -27,8 +53,6 @@ public void testChangeCursor() { SimpleCursorAdapter adapter = new SimpleCursorAdapter(context, 1, null, new String[] {"name"}, new int[] {2}, 0); - Cursor cursor = setUpDatabase(); - adapter.changeCursor(cursor); assertThat(adapter.getCursor()).isSameInstanceAs(cursor); @@ -39,8 +63,6 @@ public void testSwapCursor() { SimpleCursorAdapter adapter = new SimpleCursorAdapter(context, 1, null, new String[] {"name"}, new int[] {2}, 0); - Cursor cursor = setUpDatabase(); - adapter.swapCursor(cursor); assertThat(adapter.getCursor()).isSameInstanceAs(cursor); @@ -51,30 +73,9 @@ public void testSwapCursorToNull() { SimpleCursorAdapter adapter = new SimpleCursorAdapter(context, 1, null, new String[] {"name"}, new int[] {2}, 0); - Cursor cursor = setUpDatabase(); - adapter.swapCursor(cursor); adapter.swapCursor(null); assertThat(adapter.getCursor()).isNull(); } - - private Cursor setUpDatabase() { - SQLiteDatabase database = SQLiteDatabase.create(null); - database.execSQL("CREATE TABLE table_name(_id INT PRIMARY KEY, name VARCHAR(255));"); - String[] inserts = { - "INSERT INTO table_name (_id, name) VALUES(1234, 'Chuck');", - "INSERT INTO table_name (_id, name) VALUES(1235, 'Julie');", - "INSERT INTO table_name (_id, name) VALUES(1236, 'Chris');", - "INSERT INTO table_name (_id, name) VALUES(1237, 'Brenda');", - "INSERT INTO table_name (_id, name) VALUES(1238, 'Jane');" - }; - - for (String insert : inserts) { - database.execSQL(insert); - } - - String sql = "SELECT * FROM table_name;"; - return database.rawQuery(sql, null); - } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowUsbManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowUsbManagerTest.java index 71337a6e61f..8fba878a24d 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowUsbManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowUsbManagerTest.java @@ -18,6 +18,7 @@ import android.hardware.usb.UsbPort; import android.hardware.usb.UsbPortStatus; import android.os.Build; +import android.os.ParcelFileDescriptor; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.Arrays; @@ -194,8 +195,10 @@ public void removeDevice() { } @Test - public void openAccessory() { - assertThat(usbManager.openAccessory(usbAccessory)).isNotNull(); + public void openAccessory() throws Exception { + try (ParcelFileDescriptor pfd = usbManager.openAccessory(usbAccessory)) { + assertThat(pfd).isNotNull(); + } } @Test diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowVibratorTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowVibratorTest.java index b4963fedcc5..79dde573aa4 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowVibratorTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowVibratorTest.java @@ -2,7 +2,12 @@ import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.Q; +import static android.os.Build.VERSION_CODES.R; +import static android.os.Build.VERSION_CODES.S; import static android.os.VibrationEffect.EFFECT_CLICK; +import static android.os.VibrationEffect.EFFECT_DOUBLE_CLICK; +import static android.os.VibrationEffect.EFFECT_HEAVY_CLICK; +import static android.os.VibrationEffect.EFFECT_TICK; import static com.google.common.truth.Truth.assertThat; import static org.robolectric.Shadows.shadowOf; import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; @@ -10,8 +15,10 @@ import android.content.Context; import android.os.VibrationEffect; import android.os.Vibrator; +import android.os.vibrator.PrimitiveSegment; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.collect.ImmutableList; import java.time.Duration; import org.junit.Before; import org.junit.Test; @@ -77,6 +84,85 @@ public void vibratePredefined() { assertThat(shadowOf(vibrator).getEffectId()).isEqualTo(EFFECT_CLICK); } + @Config(minSdk = S) + @Test + public void getVibrationEffectSegments_composeOnce_shouldReturnSameFragment() { + vibrator.vibrate( + VibrationEffect.startComposition() + .addPrimitive(EFFECT_CLICK, /* scale= */ 0.5f, /* delay= */ 20) + .addPrimitive(EFFECT_CLICK, /* scale= */ 0.7f, /* delay= */ 50) + .addPrimitive(EFFECT_CLICK, /* scale= */ 0.9f, /* delay= */ 150) + .compose()); + + assertThat(shadowOf(vibrator).getVibrationEffectSegments()) + .isEqualTo( + ImmutableList.of( + new PrimitiveSegment(EFFECT_CLICK, /* scale= */ 0.5f, /* delay= */ 20), + new PrimitiveSegment(EFFECT_CLICK, /* scale= */ 0.7f, /* delay= */ 50), + new PrimitiveSegment(EFFECT_CLICK, /* scale= */ 0.9f, /* delay= */ 150))); + } + + @Config(minSdk = S) + @Test + public void getVibrationEffectSegments_composeTwice_shouldReturnTheLastComposition() { + vibrator.vibrate( + VibrationEffect.startComposition() + .addPrimitive(EFFECT_CLICK, /* scale= */ 0.5f, /* delay= */ 20) + .addPrimitive(EFFECT_CLICK, /* scale= */ 0.7f, /* delay= */ 50) + .addPrimitive(EFFECT_CLICK, /* scale= */ 0.9f, /* delay= */ 150) + .compose()); + vibrator.vibrate( + VibrationEffect.startComposition() + .addPrimitive(EFFECT_CLICK, /* scale= */ 0.4f, /* delay= */ 120) + .addPrimitive(EFFECT_CLICK, /* scale= */ 0.9f, /* delay= */ 150) + .addPrimitive(EFFECT_CLICK, /* scale= */ 1f, /* delay= */ 2150) + .compose()); + + assertThat(shadowOf(vibrator).getVibrationEffectSegments()) + .isEqualTo( + ImmutableList.of( + new PrimitiveSegment(EFFECT_CLICK, /* scale= */ 0.4f, /* delay= */ 120), + new PrimitiveSegment(EFFECT_CLICK, /* scale= */ 0.9f, /* delay= */ 150), + new PrimitiveSegment(EFFECT_CLICK, /* scale= */ 1f, /* delay= */ 2150))); + } + + @Config(minSdk = R) + @Test + public void areAllPrimitivesSupported_oneSupportedPrimitive_shouldReturnTrue() { + shadowOf(vibrator) + .setSupportedPrimitives(ImmutableList.of(EFFECT_CLICK, EFFECT_TICK, EFFECT_HEAVY_CLICK)); + + assertThat(vibrator.areAllPrimitivesSupported(EFFECT_CLICK)).isTrue(); + } + + @Config(minSdk = R) + @Test + public void areAllPrimitivesSupported_twoSupportedPrimitives_shouldReturnTrue() { + shadowOf(vibrator) + .setSupportedPrimitives(ImmutableList.of(EFFECT_CLICK, EFFECT_TICK, EFFECT_HEAVY_CLICK)); + + assertThat(vibrator.areAllPrimitivesSupported(EFFECT_TICK, EFFECT_CLICK)).isTrue(); + } + + @Config(minSdk = R) + @Test + public void areAllPrimitivesSupported_twoSupportedPrimitivesOneUnsupported_shouldReturnFalse() { + shadowOf(vibrator) + .setSupportedPrimitives(ImmutableList.of(EFFECT_CLICK, EFFECT_TICK, EFFECT_HEAVY_CLICK)); + + assertThat(vibrator.areAllPrimitivesSupported(EFFECT_TICK, EFFECT_CLICK, EFFECT_DOUBLE_CLICK)) + .isFalse(); + } + + @Config(minSdk = R) + @Test + public void areAllPrimitivesSupported_oneUnsupportedPrimitivie_shouldReturnFalse() { + shadowOf(vibrator) + .setSupportedPrimitives(ImmutableList.of(EFFECT_CLICK, EFFECT_TICK, EFFECT_HEAVY_CLICK)); + + assertThat(vibrator.areAllPrimitivesSupported(EFFECT_DOUBLE_CLICK)).isFalse(); + } + @Test public void cancelled() { vibrator.vibrate(5000); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java index de43947298e..27f012eae1a 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java @@ -241,10 +241,11 @@ public void getWallpaperFile_flagSystem_previouslyCached_shouldReturnParcelFileD /* allowBackup= */ false, WallpaperManager.FLAG_SYSTEM); - ParcelFileDescriptor parcelFileDescriptor = - manager.getWallpaperFile(WallpaperManager.FLAG_SYSTEM); + try (ParcelFileDescriptor parcelFileDescriptor = + manager.getWallpaperFile(WallpaperManager.FLAG_SYSTEM)) { assertThat(getBytesFromFileDescriptor(parcelFileDescriptor.getFileDescriptor())) .isEqualTo(getBytesFromBitmap(TEST_IMAGE_1)); + } } @Test @@ -263,10 +264,11 @@ public void getWallpaperFile_flagLock_previouslyCached_shouldReturnParcelFileDes /* allowBackup= */ false, WallpaperManager.FLAG_LOCK); - ParcelFileDescriptor parcelFileDescriptor = - manager.getWallpaperFile(WallpaperManager.FLAG_LOCK); + try (ParcelFileDescriptor parcelFileDescriptor = + manager.getWallpaperFile(WallpaperManager.FLAG_LOCK)) { assertThat(getBytesFromFileDescriptor(parcelFileDescriptor.getFileDescriptor())) .isEqualTo(getBytesFromBitmap(TEST_IMAGE_3)); + } } @Test diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiAwareManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiAwareManagerTest.java index b18a9c0eba0..1c7ce687c17 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiAwareManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiAwareManagerTest.java @@ -19,6 +19,7 @@ import android.os.Looper; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,6 +33,7 @@ public final class ShadowWifiAwareManagerTest { private Binder binder; private Handler handler; private Looper looper; + private WifiAwareSession session; private static final int CLIENT_ID = 1; @Before @@ -41,11 +43,15 @@ public void setUp() { binder = new Binder(); handler = new Handler(); looper = handler.getLooper(); - WifiAwareSession session = - ShadowWifiAwareManager.newWifiAwareSession(wifiAwareManager, binder, CLIENT_ID); + session = ShadowWifiAwareManager.newWifiAwareSession(wifiAwareManager, binder, CLIENT_ID); shadowOf(wifiAwareManager).setWifiAwareSession(session); } + @After + public void tearDown() { + session.close(); + } + @Test public void setAvailable_shouldUpdateWithAvailableStatus() { boolean available = false; @@ -86,6 +92,7 @@ public void publish_shouldPublishServiceIfWifiAwareAvailable() { shadowOf(wifiAwareManager).publish(CLIENT_ID, looper, config, testDiscoverySessionCallback); shadowMainLooper().idle(); assertThat(testDiscoverySessionCallback.publishSuccess).isTrue(); + publishDiscoverySession.close(); } @Test @@ -100,6 +107,7 @@ public void publish_shouldPublishServiceIfWifiAwareUnavailable() { wifiAwareManager.publish(CLIENT_ID, looper, config, testDiscoverySessionCallback); shadowMainLooper().idle(); assertThat(testDiscoverySessionCallback.publishSuccess).isFalse(); + publishDiscoverySession.close(); } @Test @@ -114,6 +122,7 @@ public void subscribe_shouldSubscribeIfWifiAwareAvailable() { wifiAwareManager.subscribe(CLIENT_ID, looper, config, testDiscoverySessionCallback); shadowMainLooper().idle(); assertThat(testDiscoverySessionCallback.subscribeSuccess).isTrue(); + subscribeDiscoverySession.close(); } @Test @@ -128,14 +137,16 @@ public void subscribe_shouldNotSubscribeIfWifiAwareUnavailable() { wifiAwareManager.subscribe(CLIENT_ID, looper, config, testDiscoverySessionCallback); shadowMainLooper().idle(); assertThat(testDiscoverySessionCallback.subscribeSuccess).isFalse(); + subscribeDiscoverySession.close(); } @Test public void canCreatePublishDiscoverySessionViaNewInstance() { int sessionId = 1; - PublishDiscoverySession publishDiscoverySession = - ShadowWifiAwareManager.newPublishDiscoverySession(wifiAwareManager, CLIENT_ID, sessionId); - assertThat(publishDiscoverySession).isNotNull(); + try (PublishDiscoverySession publishDiscoverySession = + ShadowWifiAwareManager.newPublishDiscoverySession(wifiAwareManager, CLIENT_ID, sessionId)) { + assertThat(publishDiscoverySession).isNotNull(); + } } @Test @@ -144,6 +155,7 @@ public void canCreateSubscribeDiscoverySessionViaNewInstance() { SubscribeDiscoverySession subscribeDiscoverySession = ShadowWifiAwareManager.newSubscribeDiscoverySession(wifiAwareManager, CLIENT_ID, sessionId); assertThat(subscribeDiscoverySession).isNotNull(); + subscribeDiscoverySession.close(); } @Test @@ -151,6 +163,7 @@ public void canCreateWifiAwareSessionViaNewInstance() { WifiAwareSession wifiAwareSession = ShadowWifiAwareManager.newWifiAwareSession(wifiAwareManager, binder, CLIENT_ID); assertThat(wifiAwareSession).isNotNull(); + wifiAwareSession.close(); } private static class TestAttachCallback extends AttachCallback { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDatePickerDialog.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDatePickerDialog.java index 2b242fd0c63..84fc3985848 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDatePickerDialog.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDatePickerDialog.java @@ -2,7 +2,6 @@ import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.KITKAT_WATCH; -import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; import static org.robolectric.shadow.api.Shadow.invokeConstructor; import static org.robolectric.util.ReflectionHelpers.ClassParameter; @@ -27,24 +26,6 @@ public class ShadowDatePickerDialog extends ShadowAlertDialog { @RealObject protected DatePickerDialog realDatePickerDialog; private Calendar calendar; - @Implementation(maxSdk = M) - protected void __constructor__( - Context context, - int theme, - DatePickerDialog.OnDateSetListener callBack, - int year, - int monthOfYear, - int dayOfMonth) { - - invokeConstructor(DatePickerDialog.class, realDatePickerDialog, - ClassParameter.from(Context.class, context), - ClassParameter.from(int.class, theme), - ClassParameter.from(DatePickerDialog.OnDateSetListener.class, callBack), - ClassParameter.from(int.class, year), - ClassParameter.from(int.class, monthOfYear), - ClassParameter.from(int.class, dayOfMonth)); - } - @Implementation(minSdk = N) protected void __constructor__( Context context, diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNode.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNode.java index 69ebf7f309f..75772d9a115 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNode.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNode.java @@ -3,6 +3,9 @@ import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.P; +import android.graphics.Camera; +import android.graphics.Matrix; +import android.graphics.Rect; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; @@ -12,6 +15,8 @@ minSdk = LOLLIPOP, maxSdk = P) public class ShadowRenderNode { + private static final float NON_ZERO_EPSILON = 0.001f; + private float alpha = 1f; private float cameraDistance; private boolean clipToOutline; @@ -28,6 +33,10 @@ public class ShadowRenderNode { private float translationX; private float translationY; private float translationZ; + private int left; + private int top; + private int right; + private int bottom; @Implementation protected boolean setAlpha(float alpha) { @@ -177,6 +186,14 @@ protected boolean isPivotExplicitlySet() { return pivotExplicitlySet; } + @Implementation + protected boolean resetPivot() { + this.pivotExplicitlySet = false; + this.pivotX = 0; + this.pivotY = 0; + return true; + } + @Implementation protected boolean setPivotX(float pivotX) { this.pivotX = pivotX; @@ -201,6 +218,144 @@ protected float getPivotY() { return pivotY; } + @Implementation + protected boolean setLeft(int left) { + this.left = left; + return true; + } + + @Implementation + protected int getLeft() { + return left; + } + + @Implementation + protected boolean setTop(int top) { + this.top = top; + return true; + } + + @Implementation + protected int getTop() { + return top; + } + + @Implementation + protected boolean setRight(int right) { + this.right = right; + return true; + } + + @Implementation + protected int getRight() { + return right; + } + + @Implementation + protected boolean setBottom(int bottom) { + this.bottom = bottom; + return true; + } + + @Implementation + protected int getBottom() { + return bottom; + } + + @Implementation + protected int getWidth() { + return right - left; + } + + @Implementation + protected int getHeight() { + return bottom - top; + } + + @Implementation + protected boolean setLeftTopRightBottom(int left, int top, int right, int bottom) { + return setPosition(left, top, right, bottom); + } + + @Implementation + protected boolean setPosition(int left, int top, int right, int bottom) { + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + return true; + } + + @Implementation + protected boolean setPosition(Rect position) { + this.left = position.left; + this.top = position.top; + this.right = position.right; + this.bottom = position.bottom; + return true; + } + + @Implementation + protected boolean offsetLeftAndRight(int offset) { + this.left += offset; + this.right += offset; + return true; + } + + @Implementation + protected boolean offsetTopAndBottom(int offset) { + this.top += offset; + this.bottom += offset; + return true; + } + + @Implementation + protected void getInverseMatrix(Matrix matrix) { + if (!isMatrixEnabled()) { + return; + } + getMatrix(matrix); + matrix.invert(matrix); + } + + @Implementation + protected void getMatrix(Matrix matrix) { + if (!isMatrixEnabled()) { + return; + } + if (!pivotExplicitlySet) { + pivotX = getWidth() / 2f; + pivotY = getHeight() / 2f; + } + matrix.reset(); + if (isZero(rotationX) && isZero(rotationY)) { + matrix.setTranslate(translationX, translationY); + matrix.preRotate(rotation, pivotX, pivotY); + matrix.preScale(scaleX, scaleY, pivotX, pivotY); + } else { + matrix.preScale(scaleX, scaleY, pivotX, pivotY); + Camera camera = new Camera(); + camera.rotateX(rotationX); + camera.rotateY(rotationY); + camera.rotateZ(-rotation); + Matrix transform = new Matrix(); + camera.getMatrix(transform); + transform.preTranslate(-pivotX, -pivotY); + transform.postTranslate(pivotX + translationX, pivotY + translationY); + matrix.postConcat(transform); + } + } + + @Implementation + protected boolean hasIdentityMatrix() { + if (!isMatrixEnabled()) { + return true; + } + Matrix matrix = new Matrix(); + getMatrix(matrix); + return matrix.isIdentity(); + } + @Implementation protected boolean isValid() { return true; @@ -229,4 +384,13 @@ protected static boolean nSetLayerType(long renderNode, int layerType) { protected static boolean nSetLayerPaint(long renderNode, long paint) { return true; } + + private static boolean isZero(float value) { + return Math.abs(value) <= NON_ZERO_EPSILON; + } + + // Temporarily allow disabling the matrix calculation during migration. + private static boolean isMatrixEnabled() { + return Boolean.parseBoolean(System.getProperty("robolectric.rendernode.enableMatrix", "false")); + } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNodeQ.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNodeQ.java index adefc71f435..62ed4b1c9f1 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNodeQ.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNodeQ.java @@ -1,5 +1,8 @@ package org.robolectric.shadows; +import android.graphics.Camera; +import android.graphics.Matrix; +import android.graphics.Rect; import android.graphics.RenderNode; import android.os.Build; import org.robolectric.annotation.Implementation; @@ -7,6 +10,8 @@ @Implements(value = RenderNode.class, isInAndroidSdk = false, minSdk = Build.VERSION_CODES.Q) public class ShadowRenderNodeQ { + private static final float NON_ZERO_EPSILON = 0.001f; + private float alpha = 1f; private float cameraDistance; private boolean clipToOutline; @@ -23,6 +28,10 @@ public class ShadowRenderNodeQ { private float translationX; private float translationY; private float translationZ; + private int left; + private int top; + private int right; + private int bottom; @Implementation protected boolean setAlpha(float alpha) { @@ -172,6 +181,14 @@ protected boolean isPivotExplicitlySet() { return pivotExplicitlySet; } + @Implementation + protected boolean resetPivot() { + this.pivotExplicitlySet = false; + this.pivotX = 0; + this.pivotY = 0; + return true; + } + @Implementation protected boolean setPivotX(float pivotX) { this.pivotX = pivotX; @@ -196,8 +213,155 @@ protected float getPivotY() { return pivotY; } + @Implementation + protected boolean setLeft(int left) { + this.left = left; + return true; + } + + @Implementation + protected int getLeft() { + return left; + } + + @Implementation + protected boolean setTop(int top) { + this.top = top; + return true; + } + + @Implementation + protected int getTop() { + return top; + } + + @Implementation + protected boolean setRight(int right) { + this.right = right; + return true; + } + + @Implementation + protected int getRight() { + return right; + } + + @Implementation + protected boolean setBottom(int bottom) { + this.bottom = bottom; + return true; + } + + @Implementation + protected int getBottom() { + return bottom; + } + + @Implementation + protected int getWidth() { + return right - left; + } + + @Implementation + protected int getHeight() { + return bottom - top; + } + + @Implementation + protected boolean setLeftTopRightBottom(int left, int top, int right, int bottom) { + return setPosition(left, top, right, bottom); + } + + @Implementation + protected boolean setPosition(int left, int top, int right, int bottom) { + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + return true; + } + + @Implementation + protected boolean setPosition(Rect position) { + this.left = position.left; + this.top = position.top; + this.right = position.right; + this.bottom = position.bottom; + return true; + } + + @Implementation + protected boolean offsetLeftAndRight(int offset) { + this.left += offset; + this.right += offset; + return true; + } + + @Implementation + protected boolean offsetTopAndBottom(int offset) { + this.top += offset; + this.bottom += offset; + return true; + } + + @Implementation + protected void getInverseMatrix(Matrix matrix) { + if (!isMatrixEnabled()) { + return; + } + getMatrix(matrix); + matrix.invert(matrix); + } + + @Implementation + protected void getMatrix(Matrix matrix) { + if (!isMatrixEnabled()) { + return; + } + if (!pivotExplicitlySet) { + pivotX = getWidth() / 2f; + pivotY = getHeight() / 2f; + } + matrix.reset(); + if (isZero(rotationX) && isZero(rotationY)) { + matrix.setTranslate(translationX, translationY); + matrix.preRotate(rotation, pivotX, pivotY); + matrix.preScale(scaleX, scaleY, pivotX, pivotY); + } else { + matrix.preScale(scaleX, scaleY, pivotX, pivotY); + Camera camera = new Camera(); + camera.rotateX(rotationX); + camera.rotateY(rotationY); + camera.rotateZ(-rotation); + Matrix transform = new Matrix(); + camera.getMatrix(transform); + transform.preTranslate(-pivotX, -pivotY); + transform.postTranslate(pivotX + translationX, pivotY + translationY); + matrix.postConcat(transform); + } + } + + @Implementation + protected boolean hasIdentityMatrix() { + if (!isMatrixEnabled()) { + return true; + } + Matrix matrix = new Matrix(); + getMatrix(matrix); + return matrix.isIdentity(); + } + @Implementation protected static boolean nIsValid(long n) { return true; } + + private static boolean isZero(float value) { + return Math.abs(value) <= NON_ZERO_EPSILON; + } + + // Temporarily allow disabling the matrix calculation during migration. + private static boolean isMatrixEnabled() { + return Boolean.parseBoolean(System.getProperty("robolectric.rendernode.enableMatrix", "false")); + } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java index 0dbcd1c2200..56aa8beaa0b 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java @@ -27,8 +27,8 @@ @Implements(value = SystemVibrator.class, isInAndroidSdk = false) public class ShadowSystemVibrator extends ShadowVibrator { - private Handler handler = new Handler(Looper.myLooper()); - private Runnable stopVibratingRunnable = () -> vibrating = false; + private final Handler handler = new Handler(Looper.getMainLooper()); + private final Runnable stopVibratingRunnable = () -> vibrating = false; @Implementation protected boolean hasVibrator() { @@ -137,6 +137,8 @@ private void recordVibratePattern(List segments, int rep pattern[i] = segment.getDuration(); i++; } + vibrationEffectSegments.clear(); + vibrationEffectSegments.addAll(segments); recordVibratePattern(pattern, repeatIndex); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVibrator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVibrator.java index 04068c46c9d..6f86aacb8a6 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVibrator.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVibrator.java @@ -1,6 +1,13 @@ package org.robolectric.shadows; +import static android.os.Build.VERSION_CODES.R; + import android.os.Vibrator; +import android.os.vibrator.VibrationEffectSegment; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; @Implements(Vibrator.class) @@ -9,6 +16,8 @@ public class ShadowVibrator { boolean cancelled; long milliseconds; protected long[] pattern; + protected final List vibrationEffectSegments = new ArrayList<>(); + protected final List supportedPrimitives = new ArrayList<>(); int repeat; boolean hasVibrator = true; boolean hasAmplitudeControl = false; @@ -73,4 +82,24 @@ public int getRepeat() { return repeat; } + /** Returns the last list of {@link VibrationEffectSegment}. */ + public List getVibrationEffectSegments() { + return vibrationEffectSegments; + } + + @Implementation(minSdk = R) + protected boolean areAllPrimitivesSupported(int... primitiveIds) { + for (int i = 0; i < primitiveIds.length; i++) { + if (!supportedPrimitives.contains(primitiveIds[i])) { + return false; + } + } + return true; + } + + /** Adds supported vibration primitives. */ + public void setSupportedPrimitives(Collection primitives) { + supportedPrimitives.clear(); + supportedPrimitives.addAll(primitives); + } }